Merge branch 'dwds-integration' of github.com:flutter/flutter into dwds-integration
diff --git a/AUTHORS b/AUTHORS
index 0fe69e4..fb6ebe3 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -68,3 +68,4 @@
Terrence Addison Tandijono(flotilla) <terrenceaddison32@gmail.com>
YeungKC <flutter@yeungkc.com>
Nobuhiro Tabuki <japanese.around30@gmail.com>
+nt4f04uNd <nt4f04und@gmail.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 70f67b9..deba2ee 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-5355f270ba99d23020307d5d2906b93338e9a60e
+defa8be2b10650dad50dfee9324ed8d16eeec13f
diff --git a/bin/internal/fuchsia-linux.version b/bin/internal/fuchsia-linux.version
index f39d43e..f1a032b 100644
--- a/bin/internal/fuchsia-linux.version
+++ b/bin/internal/fuchsia-linux.version
@@ -1 +1 @@
-ZJHmp3INUrLtYTJzHkJ-mTGQ7F59bfv1usLDP7xS-XgC
+lPMs_KwnUp27LASOqwbTfKnX1rXsDfbnstsgVJYi-zEC
diff --git a/bin/internal/fuchsia-mac.version b/bin/internal/fuchsia-mac.version
index af29bcc..7f9034b 100644
--- a/bin/internal/fuchsia-mac.version
+++ b/bin/internal/fuchsia-mac.version
@@ -1 +1 @@
-_FaRRt69ZlrCACiYukNB6u_zVpyECR3W1gPgS__sm-kC
+pZ9FgVZTKe6ZwrCLTXs-s6HRYReW0XqmPJy6dnITrPcC
diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart
deleted file mode 100644
index 5de16c5..0000000
--- a/dev/benchmarks/macrobenchmarks/lib/src/web/gallery/gallery_recorder.dart
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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 'package:flutter/material.dart';
-
-import 'package:gallery/benchmarks/gallery_automator.dart';
-
-import 'package:macrobenchmarks/src/web/recorder.dart';
-
-/// A recorder that measures frame building durations for the Gallery.
-class GalleryRecorder extends WidgetRecorder {
- GalleryRecorder({
- @required this.benchmarkName,
- this.shouldRunPredicate,
- this.testScrollsOnly = false,
- }) : assert(testScrollsOnly || shouldRunPredicate != null),
- super(name: benchmarkName, useCustomWarmUp: true);
-
- /// The name of the gallery benchmark to be run.
- final String benchmarkName;
-
- /// A function that accepts the name of a demo and returns whether we should
- /// run this demo in this benchmark.
- final bool Function(String) shouldRunPredicate;
-
- /// Whether this benchmark only tests scrolling.
- final bool testScrollsOnly;
-
- /// Whether we should continue recording.
- @override
- bool shouldContinue() => !_finished || profile.shouldContinue();
-
- GalleryAutomator _galleryAutomator;
- bool get _finished => _galleryAutomator?.finished ?? false;
-
- /// Creates the [GalleryAutomator] widget.
- @override
- Widget createWidget() {
- _galleryAutomator = GalleryAutomator(
- benchmarkName: benchmarkName,
- shouldRunPredicate: shouldRunPredicate,
- testScrollsOnly: testScrollsOnly,
- stopWarmingUpCallback: profile.stopWarmingUp,
- );
- return _galleryAutomator.createWidget();
- }
-}
diff --git a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
index e07c920..cff1edf 100644
--- a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
@@ -10,8 +10,6 @@
import 'package:macrobenchmarks/src/web/bench_text_layout.dart';
import 'package:macrobenchmarks/src/web/bench_text_out_of_picture_bounds.dart';
-import 'package:gallery/benchmarks/gallery_automator.dart' show DemoType, typeOfDemo;
-
import 'src/web/bench_build_image.dart';
import 'src/web/bench_build_material_checkbox.dart';
import 'src/web/bench_card_infinite_scroll.dart';
@@ -26,15 +24,12 @@
import 'src/web/bench_picture_recording.dart';
import 'src/web/bench_simple_lazy_text_scroll.dart';
import 'src/web/bench_text_out_of_picture_bounds.dart';
-import 'src/web/gallery/gallery_recorder.dart';
import 'src/web/recorder.dart';
typedef RecorderFactory = Recorder Function();
const bool isCanvasKit = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false);
-const String _galleryBenchmarkPrefix = 'gallery_v2';
-
/// List of all benchmarks that run in the devicelab.
///
/// When adding a new benchmark, add it to this map. Make sure that the name
@@ -67,27 +62,6 @@
BenchTextCachedLayout.canvasBenchmarkName: () => BenchTextCachedLayout(useCanvas: true),
BenchBuildColorsGrid.domBenchmarkName: () => BenchBuildColorsGrid.dom(),
BenchBuildColorsGrid.canvasBenchmarkName: () => BenchBuildColorsGrid.canvas(),
-
- // The following benchmark is for the Flutter Gallery.
- // This benchmark is failing when run with CanvasKit, so we skip it
- // for now.
- // TODO(yjbanov): https://github.com/flutter/flutter/issues/59082
- '${_galleryBenchmarkPrefix}_studies_perf': () => GalleryRecorder(
- benchmarkName: '${_galleryBenchmarkPrefix}_studies_perf',
- shouldRunPredicate: (String demo) => typeOfDemo(demo) == DemoType.study,
- ),
- '${_galleryBenchmarkPrefix}_unanimated_perf': () => GalleryRecorder(
- benchmarkName: '${_galleryBenchmarkPrefix}_unanimated_perf',
- shouldRunPredicate: (String demo) => typeOfDemo(demo) == DemoType.unanimatedWidget,
- ),
- '${_galleryBenchmarkPrefix}_animated_perf': () => GalleryRecorder(
- benchmarkName: '${_galleryBenchmarkPrefix}_animated_perf',
- shouldRunPredicate: (String demo) => typeOfDemo(demo) == DemoType.animatedWidget,
- ),
- '${_galleryBenchmarkPrefix}_scroll_perf': () => GalleryRecorder(
- benchmarkName: '${_galleryBenchmarkPrefix}_scroll_perf',
- testScrollsOnly: true,
- ),
},
};
diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml
index 8773d29..51ccdcc 100644
--- a/dev/benchmarks/macrobenchmarks/pubspec.yaml
+++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml
@@ -17,13 +17,6 @@
# flutter update-packages --force-upgrade
flutter_gallery_assets: 0.2.6
- # This is needed for web_benchmarks.
- gallery:
- git:
- url: https://github.com/flutter/gallery.git
- ref: d8b4858f88ab9006c15a5c9e197386623ea2e12c
-
- animations: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
archive: 2.0.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
args: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
async: 2.5.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -32,67 +25,28 @@
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
crypto: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- cupertino_icons: 0.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- ffi: 0.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
file: 6.0.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- flare_dart: 2.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- flare_flutter: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- flutter_localized_locales: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- flutter_staggered_grid_view: 0.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- google_fonts: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- http: 0.12.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- http_parser: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- intl: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- js: 0.6.3-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
json_rpc_2: 2.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
matcher: 0.12.10-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
meta: 1.3.0-nullsafety.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- package_info: 0.4.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
path: 1.8.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- path_provider: 1.6.14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- path_provider_linux: 0.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- path_provider_macos: 0.0.4+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- path_provider_platform_interface: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- path_provider_windows: 0.0.4+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- pedantic: 1.10.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- platform: 3.0.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- plugin_platform_interface: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- process: 4.0.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pub_semver: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- rally_assets: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- scoped_model: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- shared_preferences: 0.5.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- shared_preferences_linux: 0.0.2+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- shared_preferences_macos: 0.0.1+10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- shared_preferences_platform_interface: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- shared_preferences_web: 0.1.2+7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- shared_preferences_windows: 0.0.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- shrine_images: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_span: 1.8.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stack_trace: 1.10.0-nullsafety.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stream_channel: 2.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- string_scanner: 1.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
sync_http: 0.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
term_glyph: 1.2.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
typed_data: 1.3.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- url_launcher: 5.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- url_launcher_linux: 0.0.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- url_launcher_macos: 0.0.1+8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- url_launcher_platform_interface: 1.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- url_launcher_web: 0.1.4+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- url_launcher_windows: 0.0.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vector_math: 2.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webdriver: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- win32: 1.7.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- xdg_directories: 0.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
dev_dependencies:
flutter_test:
sdk: flutter
test: 1.16.0-nullsafety.5
- e2e: 0.7.0+1
+ integration_test: 0.9.2+1
_fe_analyzer_shared: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
analyzer: 0.39.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -104,14 +58,18 @@
fake_async: 1.2.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
html: 0.14.0+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ http: 0.12.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http_multi_server: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ http_parser: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
io: 0.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ js: 0.6.3-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
mime: 0.9.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_interop: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_io: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
node_preamble: 1.4.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
package_config: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ pedantic: 1.10.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pool: 1.5.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
shelf_packages_handler: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -119,6 +77,7 @@
shelf_web_socket: 0.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_map_stack_trace: 2.1.0-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_maps: 0.10.10-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ string_scanner: 1.1.0-nullsafety.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_api: 0.2.19-nullsafety.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_core: 0.3.12-nullsafety.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service: 4.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -249,48 +208,6 @@
- packages/flutter_gallery_assets/splash_effects/splash_effect_8.gif
- packages/flutter_gallery_assets/splash_effects/splash_effect_9.gif
- packages/flutter_gallery_assets/splash_effects/splash_effect_10.gif
- - packages/rally_assets/logo.png
- - packages/rally_assets/thumb.png
- - packages/shrine_images/diamond.png
- - packages/shrine_images/slanted_menu.png
- - packages/shrine_images/0-0.jpg
- - packages/shrine_images/1-0.jpg
- - packages/shrine_images/2-0.jpg
- - packages/shrine_images/3-0.jpg
- - packages/shrine_images/4-0.jpg
- - packages/shrine_images/5-0.jpg
- - packages/shrine_images/6-0.jpg
- - packages/shrine_images/7-0.jpg
- - packages/shrine_images/8-0.jpg
- - packages/shrine_images/9-0.jpg
- - packages/shrine_images/10-0.jpg
- - packages/shrine_images/11-0.jpg
- - packages/shrine_images/12-0.jpg
- - packages/shrine_images/13-0.jpg
- - packages/shrine_images/14-0.jpg
- - packages/shrine_images/15-0.jpg
- - packages/shrine_images/16-0.jpg
- - packages/shrine_images/17-0.jpg
- - packages/shrine_images/18-0.jpg
- - packages/shrine_images/19-0.jpg
- - packages/shrine_images/20-0.jpg
- - packages/shrine_images/21-0.jpg
- - packages/shrine_images/22-0.jpg
- - packages/shrine_images/23-0.jpg
- - packages/shrine_images/24-0.jpg
- - packages/shrine_images/25-0.jpg
- - packages/shrine_images/26-0.jpg
- - packages/shrine_images/27-0.jpg
- - packages/shrine_images/28-0.jpg
- - packages/shrine_images/29-0.jpg
- - packages/shrine_images/30-0.jpg
- - packages/shrine_images/31-0.jpg
- - packages/shrine_images/32-0.jpg
- - packages/shrine_images/33-0.jpg
- - packages/shrine_images/34-0.jpg
- - packages/shrine_images/35-0.jpg
- - packages/shrine_images/36-0.jpg
- - packages/shrine_images/37-0.jpg
# The following font is required for running Flutter Gallery benchmarks.
fonts:
@@ -298,4 +215,4 @@
fonts:
- asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf
-# PUBSPEC CHECKSUM: 7d7b
+# PUBSPEC CHECKSUM: c81b
diff --git a/dev/benchmarks/macrobenchmarks/test/frame_policy.dart b/dev/benchmarks/macrobenchmarks/test/frame_policy.dart
index 93e4a0a..1290185 100644
--- a/dev/benchmarks/macrobenchmarks/test/frame_policy.dart
+++ b/dev/benchmarks/macrobenchmarks/test/frame_policy.dart
@@ -7,13 +7,13 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:e2e/e2e.dart';
+import 'package:integration_test/integration_test.dart';
import 'package:macrobenchmarks/src/simple_scroll.dart';
void main() {
- final E2EWidgetsFlutterBinding binding =
- E2EWidgetsFlutterBinding.ensureInitialized() as E2EWidgetsFlutterBinding;
+ final IntegrationTestWidgetsFlutterBinding binding =
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding;
testWidgets(
'Frame Counter and Input Delay for benchmarkLive',
(WidgetTester tester) async {
diff --git a/dev/benchmarks/macrobenchmarks/test/util.dart b/dev/benchmarks/macrobenchmarks/test/util.dart
index ce236b0..f3c5b8a 100644
--- a/dev/benchmarks/macrobenchmarks/test/util.dart
+++ b/dev/benchmarks/macrobenchmarks/test/util.dart
@@ -2,14 +2,11 @@
// 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/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:macrobenchmarks/common.dart';
-import 'package:e2e/e2e.dart';
+import 'package:integration_test/integration_test.dart';
import 'package:macrobenchmarks/main.dart' as app;
typedef ControlCallback = Future<void> Function(WidgetController controller);
@@ -23,9 +20,9 @@
ControlCallback body,
ControlCallback setup,
}) {
- final WidgetsBinding _binding = E2EWidgetsFlutterBinding.ensureInitialized();
- assert(_binding is E2EWidgetsFlutterBinding);
- final E2EWidgetsFlutterBinding binding = _binding as E2EWidgetsFlutterBinding;
+ final WidgetsBinding _binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+ assert(_binding is IntegrationTestWidgetsFlutterBinding);
+ final IntegrationTestWidgetsFlutterBinding binding = _binding as IntegrationTestWidgetsFlutterBinding;
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive;
testWidgets(testName, (WidgetTester tester) async {
@@ -60,7 +57,7 @@
await setup(tester);
}
- await watchPerformance(binding, () async {
+ await binding.watchPerformance(() async {
final Future<void> durationFuture = tester.binding.delayed(duration);
if (body != null) {
await body(tester);
@@ -69,29 +66,3 @@
});
}, semanticsEnabled: false, timeout: Timeout(timeout));
}
-
-bool _firstRun = true;
-
-// TODO(CareF): move this to e2e after FrameTimingSummarizer goes into stable
-// branch (#63537)
-/// watches the [FrameTiming] of `action` and report it to the e2e binding.
-Future<void> watchPerformance(
- E2EWidgetsFlutterBinding binding,
- Future<void> action(), {
- String reportKey = 'performance',
-}) async {
- assert(() {
- if (_firstRun) {
- debugPrint(kDebugWarning);
- _firstRun = false;
- }
- return true;
- }());
- final List<FrameTiming> frameTimings = <FrameTiming>[];
- final TimingsCallback watcher = frameTimings.addAll;
- binding.addTimingsCallback(watcher);
- await action();
- binding.removeTimingsCallback(watcher);
- final FrameTimingSummarizer frameTimes = FrameTimingSummarizer(frameTimings);
- binding.reportData = <String, dynamic>{reportKey: frameTimes.summary};
-}
diff --git a/dev/benchmarks/macrobenchmarks/test_driver/e2e_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/e2e_test.dart
index a7d388e..d931eff 100644
--- a/dev/benchmarks/macrobenchmarks/test_driver/e2e_test.dart
+++ b/dev/benchmarks/macrobenchmarks/test_driver/e2e_test.dart
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:e2e/e2e_driver.dart' as driver;
+import 'package:integration_test/integration_test_driver.dart' as driver;
-Future<void> main() => driver.e2eDriver(
+Future<void> main() => driver.integrationDriver(
timeout: const Duration(minutes: 5),
responseDataCallback: (Map<String, dynamic> data) async {
await driver.writeResponseData(
diff --git a/dev/benchmarks/macrobenchmarks/test_driver/frame_policy_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/frame_policy_test.dart
index 4e93f34..1d1318e 100644
--- a/dev/benchmarks/macrobenchmarks/test_driver/frame_policy_test.dart
+++ b/dev/benchmarks/macrobenchmarks/test_driver/frame_policy_test.dart
@@ -2,29 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:convert';
import 'dart:io';
-import 'package:e2e/common.dart' as e2e;
-import 'package:flutter_driver/flutter_driver.dart';
+import 'package:integration_test/integration_test_driver.dart' as driver;
-import 'package:path/path.dart' as path;
+Future<void> main() => driver.integrationDriver(
+ timeout: const Duration(minutes: 1),
+ responseDataCallback: (Map<String, dynamic> data) async {
+ final Map<String, dynamic> benchmarkLiveResult =
+ data['benchmarkLive'] as Map<String,dynamic>;
+ final Map<String, dynamic> fullyLiveResult =
+ data['fullyLive'] as Map<String,dynamic>;
-Future<void> main() async {
- const Duration timeout = Duration(minutes: 1);
- const String testName = 'frame_policy';
-
- final FlutterDriver driver = await FlutterDriver.connect();
- String jsonResult;
- jsonResult = await driver.requestData(null, timeout: timeout);
- final e2e.Response response = e2e.Response.fromJson(jsonResult);
- await driver.close();
- final Map<String, dynamic> benchmarkLiveResult =
- response.data['benchmarkLive'] as Map<String,dynamic>;
- final Map<String, dynamic> fullyLiveResult =
- response.data['fullyLive'] as Map<String,dynamic>;
-
- if (response.allTestsPassed) {
if(benchmarkLiveResult['frame_count'] as int < 10
|| fullyLiveResult['frame_count'] as int < 10) {
print('Failure Details:\nNot Enough frames collected:'
@@ -32,22 +21,12 @@
'${fullyLiveResult['frameCount']}.');
exit(1);
}
- print('All tests passed.');
- const String destinationDirectory = 'build';
- await fs.directory(destinationDirectory).create(recursive: true);
- final File file = fs.file(path.join(
- destinationDirectory,
- '${testName}_event_delay.json'
- ));
- await file.writeAsString(const JsonEncoder.withIndent(' ').convert(
+ await driver.writeResponseData(
<String, dynamic>{
'benchmarkLive': benchmarkLiveResult,
'fullyLive': fullyLiveResult,
},
- ));
- exit(0);
- } else {
- print('Failure Details:\n${response.formattedFailureDetails}');
- exit(1);
+ testOutputFilename: 'frame_policy_event_delay',
+ );
}
-}
+);
diff --git a/dev/bots/docs.sh b/dev/bots/docs.sh
index 21dbde1..a22faa2 100755
--- a/dev/bots/docs.sh
+++ b/dev/bots/docs.sh
@@ -20,7 +20,7 @@
# Install and activate dartdoc.
# NOTE: When updating to a new dartdoc version, please also update
# `dartdoc_options.yaml` to include newly introduced error and warning types.
- "$PUB" global activate dartdoc 0.35.0
+ "$PUB" global activate dartdoc 0.36.0
# This script generates a unified doc set, and creates
# a custom index.html, placing everything into dev/docs/doc.
diff --git a/dev/bots/run_command.dart b/dev/bots/run_command.dart
index 413fdd4..bc04450 100644
--- a/dev/bots/run_command.dart
+++ b/dev/bots/run_command.dart
@@ -2,92 +2,113 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'dart:convert';
import 'dart:core' hide print;
-import 'dart:io' hide exit;
+import 'dart:io' as io;
import 'package:path/path.dart' as path;
import 'utils.dart';
-// TODO(ianh): These two functions should be refactored into something that avoids all this code duplication.
-
+/// Runs the `executable` and returns standard output as a stream of lines.
+///
+/// The returned stream reaches its end immediately after the command exits.
+///
+/// If `expectNonZeroExit` is false and the process exits with a non-zero exit
+/// code fails the test immediately by exiting the test process with exit code
+/// 1.
Stream<String> runAndGetStdout(String executable, List<String> arguments, {
String workingDirectory,
Map<String, String> environment,
bool expectNonZeroExit = false,
- int expectedExitCode,
- String failureMessage,
- bool skip = false,
}) async* {
- final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
- final String relativeWorkingDir = path.relative(workingDirectory);
- if (skip) {
- printProgress('SKIPPING', relativeWorkingDir, commandDescription);
- return;
- }
- printProgress('RUNNING', relativeWorkingDir, commandDescription);
-
- final Stopwatch time = Stopwatch()..start();
- final Process process = await Process.start(executable, arguments,
+ final StreamController<String> output = StreamController<String>();
+ final Future<CommandResult> command = runCommand(
+ executable,
+ arguments,
workingDirectory: workingDirectory,
environment: environment,
+ expectNonZeroExit: expectNonZeroExit,
+ // Capture the output so it's not printed to the console by default.
+ outputMode: OutputMode.capture,
+ outputListener: (String line, io.Process process) {
+ output.add(line);
+ },
);
- stderr.addStream(process.stderr);
- final Stream<String> lines = process.stdout.transform(utf8.decoder).transform(const LineSplitter());
- yield* lines;
+ // Close the stream controller after the command is complete. Otherwise,
+ // the yield* will never finish.
+ command.whenComplete(output.close);
- final int exitCode = await process.exitCode;
- if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
- exitWithError(<String>[
- if (failureMessage != null)
- failureMessage
- else
- '${bold}ERROR: ${red}Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset',
- '${bold}Command: $green$commandDescription$reset',
- '${bold}Relative working directory: $cyan$relativeWorkingDir$reset',
- ]);
- }
- print('$clock ELAPSED TIME: ${prettyPrintDuration(time.elapsed)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
+ yield* output.stream;
}
-/// Runs the `executable` and waits until the process exits.
-///
-/// If the process exits with a non-zero exit code, exits this process with
-/// exit code 1, unless `expectNonZeroExit` is set to true.
+/// Represents a running process launched using [startCommand].
+class Command {
+ Command._(this.process, this._time, this._savedStdout, this._savedStderr);
+
+ /// The raw process that was launched for this command.
+ final io.Process process;
+
+ final Stopwatch _time;
+ final Future<List<List<int>>> _savedStdout;
+ final Future<List<List<int>>> _savedStderr;
+
+ /// Evaluates when the [process] exits.
+ ///
+ /// Returns the result of running the command.
+ Future<CommandResult> get onExit async {
+ final int exitCode = await process.exitCode;
+ _time.stop();
+
+ // Saved output is null when OutputMode.print is used.
+ final String flattenedStdout = _savedStdout != null ? _flattenToString(await _savedStdout) : null;
+ final String flattenedStderr = _savedStderr != null ? _flattenToString(await _savedStderr) : null;
+ return CommandResult._(exitCode, _time.elapsed, flattenedStdout, flattenedStderr);
+ }
+}
+
+/// The result of running a command using [startCommand] and [runCommand];
+class CommandResult {
+ CommandResult._(this.exitCode, this.elapsedTime, this.flattenedStdout, this.flattenedStderr);
+
+ /// The exit code of the process.
+ final int exitCode;
+
+ /// The amount of time it took the process to complete.
+ final Duration elapsedTime;
+
+ /// Standard output decoded as a string using UTF8 decoder.
+ final String flattenedStdout;
+
+ /// Standard error output decoded as a string using UTF8 decoder.
+ final String flattenedStderr;
+}
+
+/// Starts the `executable` and returns a command object representing the
+/// running process.
///
/// `outputListener` is called for every line of standard output from the
/// process, and is given the [Process] object. This can be used to interrupt
/// an indefinitely running process, for example, by waiting until the process
/// emits certain output.
-Future<void> runCommand(String executable, List<String> arguments, {
+///
+/// `outputMode` controls where the standard output from the command process
+/// goes. See [OutputMode].
+Future<Command> startCommand(String executable, List<String> arguments, {
String workingDirectory,
Map<String, String> environment,
- bool expectNonZeroExit = false,
- int expectedExitCode,
- String failureMessage,
OutputMode outputMode = OutputMode.print,
- CapturedOutput output,
- bool skip = false,
bool Function(String) removeLine,
- void Function(String, Process) outputListener,
+ void Function(String, io.Process) outputListener,
}) async {
- assert(
- (outputMode == OutputMode.capture) == (output != null),
- 'The output parameter must be non-null with and only with OutputMode.capture',
- );
-
final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
- final String relativeWorkingDir = path.relative(workingDirectory ?? Directory.current.path);
- if (skip) {
- printProgress('SKIPPING', relativeWorkingDir, commandDescription);
- return;
- }
+ final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
printProgress('RUNNING', relativeWorkingDir, commandDescription);
final Stopwatch time = Stopwatch()..start();
- final Process process = await Process.start(executable, arguments,
+ final io.Process process = await io.Process.start(executable, arguments,
workingDirectory: workingDirectory,
environment: environment,
);
@@ -108,56 +129,103 @@
switch (outputMode) {
case OutputMode.print:
await Future.wait<void>(<Future<void>>[
- stdout.addStream(stdoutSource),
- stderr.addStream(process.stderr),
+ io.stdout.addStream(stdoutSource),
+ io.stderr.addStream(process.stderr),
]);
break;
case OutputMode.capture:
- case OutputMode.discard:
savedStdout = stdoutSource.toList();
savedStderr = process.stderr.toList();
break;
}
- final int exitCode = await process.exitCode;
- if (output != null) {
- output.stdout = _flattenToString(await savedStdout);
- output.stderr = _flattenToString(await savedStderr);
+ return Command._(process, time, savedStdout, savedStderr);
+}
+
+/// Runs the `executable` and waits until the process exits.
+///
+/// If the process exits with a non-zero exit code, exits this process with
+/// exit code 1, unless `expectNonZeroExit` is set to true.
+///
+/// `outputListener` is called for every line of standard output from the
+/// process, and is given the [Process] object. This can be used to interrupt
+/// an indefinitely running process, for example, by waiting until the process
+/// emits certain output.
+///
+/// Returns the result of the finished process, or null if `skip` is true.
+///
+/// `outputMode` controls where the standard output from the command process
+/// goes. See [OutputMode].
+Future<CommandResult> runCommand(String executable, List<String> arguments, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool expectNonZeroExit = false,
+ int expectedExitCode,
+ String failureMessage,
+ OutputMode outputMode = OutputMode.print,
+ bool skip = false,
+ bool Function(String) removeLine,
+ void Function(String, io.Process) outputListener,
+}) async {
+ final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
+ final String relativeWorkingDir = path.relative(workingDirectory ?? io.Directory.current.path);
+ if (skip) {
+ printProgress('SKIPPING', relativeWorkingDir, commandDescription);
+ return null;
}
- if ((exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && exitCode != expectedExitCode)) {
+ final Command command = await startCommand(executable, arguments,
+ workingDirectory: workingDirectory,
+ environment: environment,
+ outputMode: outputMode,
+ removeLine: removeLine,
+ outputListener: outputListener,
+ );
+
+ final CommandResult result = await command.onExit;
+
+ if ((result.exitCode == 0) == expectNonZeroExit || (expectedExitCode != null && result.exitCode != expectedExitCode)) {
// Print the output when we get unexpected results (unless output was
// printed already).
switch (outputMode) {
case OutputMode.print:
break;
case OutputMode.capture:
- case OutputMode.discard:
- stdout.writeln(_flattenToString(await savedStdout));
- stderr.writeln(_flattenToString(await savedStderr));
+ io.stdout.writeln(result.flattenedStdout);
+ io.stderr.writeln(result.flattenedStderr);
break;
}
exitWithError(<String>[
if (failureMessage != null)
failureMessage
else
- '${bold}ERROR: ${red}Last command exited with $exitCode (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset',
+ '${bold}ERROR: ${red}Last command exited with ${result.exitCode} (expected: ${expectNonZeroExit ? (expectedExitCode ?? 'non-zero') : 'zero'}).$reset',
'${bold}Command: $green$commandDescription$reset',
'${bold}Relative working directory: $cyan$relativeWorkingDir$reset',
]);
}
- print('$clock ELAPSED TIME: ${prettyPrintDuration(time.elapsed)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
+ print('$clock ELAPSED TIME: ${prettyPrintDuration(result.elapsedTime)} for $green$commandDescription$reset in $cyan$relativeWorkingDir$reset');
+ return result;
}
/// Flattens a nested list of UTF-8 code units into a single string.
String _flattenToString(List<List<int>> chunks) =>
utf8.decode(chunks.expand<int>((List<int> ints) => ints).toList());
-/// Specifies what to do with command output from [runCommand].
-enum OutputMode { print, capture, discard }
+/// Specifies what to do with the command output from [runCommand] and [startCommand].
+enum OutputMode {
+ /// Forwards standard output and standard error streams to the test process'
+ /// respective standard streams.
+ ///
+ /// Use this mode if all you want is print the output of the command to the
+ /// console. The output is no longer available after the process exits.
+ print,
-/// Stores command output from [runCommand] when used with [OutputMode.capture].
-class CapturedOutput {
- String stdout;
- String stderr;
+ /// Saves standard output and standard error streams in memory.
+ ///
+ /// Captured output can be retrieved from the [CommandResult] object.
+ ///
+ /// Use this mode in tests that need to inspect the output of a command, or
+ /// when the output should not be printed to console.
+ capture,
}
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index 84ba99a..b21dec1 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -26,7 +26,7 @@
///
/// If the output does not match expectations, the function shall return an
/// appropriate error message.
-typedef OutputChecker = String Function(CapturedOutput);
+typedef OutputChecker = String Function(CommandResult);
final String exe = Platform.isWindows ? '.exe' : '';
final String bat = Platform.isWindows ? '.bat' : '';
@@ -143,9 +143,8 @@
return;
}
final String expectedVersion = File(engineVersionFile).readAsStringSync().trim();
- final CapturedOutput flutterTesterOutput = CapturedOutput();
- await runCommand(flutterTester, <String>['--help'], output: flutterTesterOutput, outputMode: OutputMode.capture);
- final String actualVersion = flutterTesterOutput.stderr.split('\n').firstWhere((final String line) {
+ final CommandResult result = await runCommand(flutterTester, <String>['--help'], outputMode: OutputMode.capture);
+ final String actualVersion = result.flattenedStderr.split('\n').firstWhere((final String line) {
return line.startsWith('Flutter Engine Version:');
});
if (!actualVersion.contains(expectedVersion)) {
@@ -190,8 +189,8 @@
script: path.join('test_smoke_test', 'pending_timer_fail_test.dart'),
expectFailure: true,
printOutput: false,
- outputChecker: (CapturedOutput output) {
- return output.stdout.contains('failingPendingTimerTest')
+ outputChecker: (CommandResult result) {
+ return result.flattenedStdout.contains('failingPendingTimerTest')
? null
: 'Failed to find the stack trace for the pending Timer.';
}
@@ -230,7 +229,7 @@
<String>['drive', '--use-existing-app', '-t', path.join('test_driver', 'failure.dart')],
workingDirectory: path.join(flutterRoot, 'packages', 'flutter_driver'),
expectNonZeroExit: true,
- outputMode: OutputMode.discard,
+ outputMode: OutputMode.capture,
),
],
);
@@ -287,7 +286,7 @@
'--report-on=lib/'
],
workingDirectory: toolRoot,
- outputMode: OutputMode.discard,
+ outputMode: OutputMode.capture,
);
}
@@ -529,7 +528,8 @@
Future<void> _runFrameworkTests() async {
final bq.BigqueryApi bigqueryApi = await _getBigqueryApi();
- final List<String> nullSafetyOptions = <String>['--null-assertions', '--no-sound-null-safety'];
+ final List<String> soundNullSafetyOptions = <String>['--enable-experiment=non-nullable', '--null-assertions', '--sound-null-safety'];
+ final List<String> mixedModeNullSafetyOptions = <String>['--enable-experiment=non-nullable', '--null-assertions', '--no-sound-null-safety'];
final List<String> trackWidgetCreationAlternatives = <String>['--track-widget-creation', '--no-track-widget-creation'];
Future<void> runWidgets() async {
@@ -537,7 +537,7 @@
for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
await _runFlutterTest(
path.join(flutterRoot, 'packages', 'flutter'),
- options: <String>[trackWidgetCreationOption, ...nullSafetyOptions],
+ options: <String>[trackWidgetCreationOption, ...soundNullSafetyOptions],
tableData: bigqueryApi?.tabledata,
tests: <String>[ path.join('test', 'widgets') + path.separator ],
);
@@ -563,7 +563,7 @@
for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
await _runFlutterTest(
path.join(flutterRoot, 'packages', 'flutter'),
- options: <String>[trackWidgetCreationOption, ...nullSafetyOptions],
+ options: <String>[trackWidgetCreationOption, ...soundNullSafetyOptions],
tableData: bigqueryApi?.tabledata,
tests: tests,
);
@@ -614,14 +614,14 @@
await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'), tableData: bigqueryApi?.tabledata);
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'), tableData: bigqueryApi?.tabledata);
await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'), tableData: bigqueryApi?.tabledata);
- await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), tableData: bigqueryApi?.tabledata);
+ await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), tableData: bigqueryApi?.tabledata, options: mixedModeNullSafetyOptions);
await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks'), tableData: bigqueryApi?.tabledata);
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tableData: bigqueryApi?.tabledata, tests: <String>[path.join('test', 'src', 'real_tests')]);
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens'), tableData: bigqueryApi?.tabledata);
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata);
- await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata, options: nullSafetyOptions);
+ await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata, options: soundNullSafetyOptions);
await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), tableData: bigqueryApi?.tabledata);
- await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: nullSafetyOptions);
+ await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: mixedModeNullSafetyOptions);
await _runFlutterTest(
path.join(flutterRoot, 'dev', 'tracing_tests'),
options: <String>['--enable-vmservice'],
@@ -642,11 +642,11 @@
script: path.join('test', 'bindings_test_failure.dart'),
expectFailure: true,
printOutput: false,
- outputChecker: (CapturedOutput output) {
- final Iterable<Match> matches = httpClientWarning.allMatches(output.stdout);
+ outputChecker: (CommandResult result) {
+ final Iterable<Match> matches = httpClientWarning.allMatches(result.flattenedStdout);
if (matches == null || matches.isEmpty || matches.length > 1) {
return 'Failed to print warning about HttpClientUsage, or printed it too many times.\n'
- 'stdout:\n${output.stdout}';
+ 'stdout:\n${result.flattenedStdout}';
}
return null;
},
@@ -868,9 +868,8 @@
List<String> additionalArguments = const<String>[],
}) async {
final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
- final CapturedOutput output = CapturedOutput();
bool success = false;
- await runCommand(
+ final CommandResult result = await runCommand(
flutter,
<String>[
'run',
@@ -889,7 +888,6 @@
'-t',
target,
],
- output: output,
outputMode: OutputMode.capture,
outputListener: (String line, Process process) {
if (line.contains('--- TEST SUCCEEDED ---')) {
@@ -908,7 +906,8 @@
if (success) {
print('${green}Web stack trace integration test passed.$reset');
} else {
- print(output.stdout);
+ print(result.flattenedStdout);
+ print(result.flattenedStderr);
print('${red}Web stack trace integration test failed.$reset');
exit(1);
}
@@ -1125,29 +1124,22 @@
args.addAll(tests);
if (!shouldProcessOutput) {
- OutputMode outputMode = OutputMode.discard;
- CapturedOutput output;
+ final OutputMode outputMode = outputChecker == null && printOutput
+ ? OutputMode.print
+ : OutputMode.capture;
- if (outputChecker != null) {
- outputMode = OutputMode.capture;
- output = CapturedOutput();
- } else if (printOutput) {
- outputMode = OutputMode.print;
- }
-
- await runCommand(
+ final CommandResult result = await runCommand(
flutter,
args,
workingDirectory: workingDirectory,
expectNonZeroExit: expectFailure,
outputMode: outputMode,
- output: output,
skip: skip,
environment: environment,
);
if (outputChecker != null) {
- final String message = outputChecker(output);
+ final String message = outputChecker(result);
if (message != null)
exitWithError(<String>[message]);
}
diff --git a/dev/customer_testing/pubspec.yaml b/dev/customer_testing/pubspec.yaml
index 0471daa..8036429 100644
--- a/dev/customer_testing/pubspec.yaml
+++ b/dev/customer_testing/pubspec.yaml
@@ -2,7 +2,7 @@
description: Tool to run the tests listed in the flutter/tests repository.
environment:
- sdk: any
+ sdk: '>=2.9.0 <3.0.0'
dependencies:
args: 1.6.0
diff --git a/dev/devicelab/bin/run.dart b/dev/devicelab/bin/run.dart
index 71badfd..2b34d5e 100644
--- a/dev/devicelab/bin/run.dart
+++ b/dev/devicelab/bin/run.dart
@@ -9,6 +9,7 @@
import 'package:path/path.dart' as path;
import 'package:flutter_devicelab/framework/ab.dart';
+import 'package:flutter_devicelab/framework/cocoon.dart';
import 'package:flutter_devicelab/framework/manifest.dart';
import 'package:flutter_devicelab/framework/runner.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
@@ -18,8 +19,8 @@
List<String> _taskNames = <String>[];
-/// Suppresses standard output, prints only standard error output.
-bool silent;
+/// The device-id to run test on.
+String deviceId;
/// The build of the local engine to use.
///
@@ -32,8 +33,13 @@
/// Whether to exit on first test failure.
bool exitOnFirstTestFailure;
-/// The device-id to run test on.
-String deviceId;
+/// File containing a service account token.
+///
+/// If passed, the test run results will be uploaded to Flutter infrastructure.
+String serviceAccountTokenFile;
+
+/// Suppresses standard output, prints only standard error output.
+bool silent;
/// Runs tasks.
///
@@ -74,11 +80,12 @@
return;
}
- silent = args['silent'] as bool;
+ deviceId = args['device-id'] as String;
localEngine = args['local-engine'] as String;
localEngineSrcPath = args['local-engine-src-path'] as String;
exitOnFirstTestFailure = args['exit'] as bool;
- deviceId = args['device-id'] as String;
+ serviceAccountTokenFile = args['service-account-token-file'] as String;
+ silent = args['silent'] as bool;
if (args.wasParsed('ab')) {
await _runABTest();
@@ -102,6 +109,11 @@
print(const JsonEncoder.withIndent(' ').convert(result));
section('Finished task "$taskName"');
+ if (serviceAccountTokenFile != null) {
+ final Cocoon cocoon = Cocoon(serviceAccountTokenPath: serviceAccountTokenFile);
+ await cocoon.sendTaskResult(taskName: taskName, result: result);
+ }
+
if (!result.succeeded) {
exitCode = 1;
if (exitOnFirstTestFailure) {
@@ -335,6 +347,10 @@
'`required_agent_capabilities`\nin the `manifest.yaml` file.',
)
..addOption(
+ 'service-account-token-file',
+ help: '[Flutter infrastructure] Authentication for uploading results.',
+ )
+ ..addOption(
'stage',
abbr: 's',
help: 'Name of the stage. Runs all tasks for that stage. The tasks and\n'
diff --git a/dev/devicelab/bin/tasks/android_defines_test.dart b/dev/devicelab/bin/tasks/android_defines_test.dart
index ff4cdc7..c583efb 100644
--- a/dev/devicelab/bin/tasks/android_defines_test.dart
+++ b/dev/devicelab/bin/tasks/android_defines_test.dart
@@ -4,10 +4,10 @@
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/defines_task.dart';
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
/// Verify that dart defines work on Android.
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
- await task(runDartDefinesTask);
+ await task(dartDefinesTask());
}
diff --git a/dev/devicelab/bin/tasks/commands_test.dart b/dev/devicelab/bin/tasks/commands_test.dart
deleted file mode 100644
index 057770c..0000000
--- a/dev/devicelab/bin/tasks/commands_test.dart
+++ /dev/null
@@ -1,134 +0,0 @@
-// 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:async';
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:path/path.dart' as path;
-import 'package:vm_service_client/vm_service_client.dart';
-
-import 'package:flutter_devicelab/framework/adb.dart';
-import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/framework/task_result.dart';
-import 'package:flutter_devicelab/framework/utils.dart';
-
-void main() {
- task(() async {
- int vmServicePort;
-
- final Device device = await devices.workingDevice;
- await device.unlock();
- final Directory appDir = dir(path.join(flutterDirectory.path, 'dev/integration_tests/ui'));
- await inDirectory(appDir, () async {
- final Completer<void> ready = Completer<void>();
- bool ok;
- print('run: starting...');
- final Process run = await startProcess(
- path.join(flutterDirectory.path, 'bin', 'flutter'),
- <String>['run', '--verbose', '--disable-service-auth-codes', '--no-fast-start', '-d', device.deviceId, 'lib/commands.dart'],
- );
- final StreamController<String> stdout = StreamController<String>.broadcast();
- run.stdout
- .transform<String>(utf8.decoder)
- .transform<String>(const LineSplitter())
- .listen((String line) {
- print('run:stdout: $line');
- stdout.add(line);
- if (vmServicePort == null) {
- vmServicePort = parseServicePort(line);
- if (vmServicePort != null) {
- print('service protocol connection available at port $vmServicePort');
- print('run: ready!');
- ready.complete();
- ok ??= true;
- }
- }
- });
- run.stderr
- .transform<String>(utf8.decoder)
- .transform<String>(const LineSplitter())
- .listen((String line) {
- stderr.writeln('run:stderr: $line');
- });
- run.exitCode.then<void>((int exitCode) { ok = false; });
- await Future.any<dynamic>(<Future<dynamic>>[ ready.future, run.exitCode ]);
- if (!ok)
- throw 'Failed to run test app.';
-
- final VMServiceClient client = VMServiceClient.connect('ws://localhost:$vmServicePort/ws');
-
- final DriveHelper driver = DriveHelper(vmServicePort);
-
- await driver.drive('none');
- print('test: pressing "p" to enable debugPaintSize...');
- run.stdin.write('p');
- await driver.drive('debug_paint');
- print('test: pressing "p" again...');
- run.stdin.write('p');
- await driver.drive('none');
- print('test: pressing "P" to enable performance overlay...');
- run.stdin.write('P');
- await driver.drive('performance_overlay');
- print('test: pressing "P" again...');
- run.stdin.write('P');
- await driver.drive('none');
- final Future<String> reloadStartingText =
- stdout.stream.firstWhere((String line) => line.endsWith('] Performing hot reload...'));
- final Future<String> reloadEndingText =
- stdout.stream.firstWhere((String line) => line.contains('] Reloaded ') && line.endsWith('ms.'));
- print('test: pressing "r" to perform a hot reload...');
- run.stdin.write('r');
- await reloadStartingText;
- await reloadEndingText;
- await driver.drive('none');
- final Future<String> restartStartingText =
- stdout.stream.firstWhere((String line) => line.endsWith('Performing hot restart...'));
- final Future<String> restartEndingText =
- stdout.stream.firstWhere((String line) => line.contains('] Restarted application in '));
- print('test: pressing "R" to perform a full reload...');
- run.stdin.write('R');
- await restartStartingText;
- await restartEndingText;
- await driver.drive('none');
- run.stdin.write('q');
- final int result = await run.exitCode;
- if (result != 0)
- throw 'Received unexpected exit code $result from run process.';
- print('test: validating that the app has in fact closed...');
- await client.done.timeout(const Duration(seconds: 5));
- });
- return TaskResult.success(null);
- });
-}
-
-class DriveHelper {
- DriveHelper(this.vmServicePort);
-
- final int vmServicePort;
-
- Future<void> drive(String name) async {
- print('drive: running commands_$name check...');
- final Process drive = await startProcess(
- path.join(flutterDirectory.path, 'bin', 'flutter'),
- <String>['drive', '--use-existing-app', 'http://127.0.0.1:$vmServicePort/', '--keep-app-running', '--driver', 'test_driver/commands_${name}_test.dart'],
- );
- drive.stdout
- .transform<String>(utf8.decoder)
- .transform<String>(const LineSplitter())
- .listen((String line) {
- print('drive:stdout: $line');
- });
- drive.stderr
- .transform<String>(utf8.decoder)
- .transform<String>(const LineSplitter())
- .listen((String line) {
- stderr.writeln('drive:stderr: $line');
- });
- final int result = await drive.exitCode;
- if (result != 0)
- throw 'Failed to drive test app (exit code $result).';
- print('drive: finished commands_$name check successfully.');
- }
-}
diff --git a/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart b/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart
index 83316b7..ade3b84 100644
--- a/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart
+++ b/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart
@@ -21,6 +21,7 @@
await inDirectory(complexLayoutPath, () async {
await flutter('drive', options: <String>[
+ '--no-android-gradle-daemon',
'-v',
'--profile',
'--trace-startup', // Enables "endless" timeline event buffering.
diff --git a/dev/devicelab/bin/tasks/flutter_gallery_sksl_warmup_ios32__transition_perf.dart b/dev/devicelab/bin/tasks/flutter_gallery_sksl_warmup_ios32__transition_perf.dart
deleted file mode 100644
index 60fc9b5..0000000
--- a/dev/devicelab/bin/tasks/flutter_gallery_sksl_warmup_ios32__transition_perf.dart
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 'package:flutter_devicelab/tasks/perf_tests.dart';
-import 'package:flutter_devicelab/framework/adb.dart';
-import 'package:flutter_devicelab/framework/framework.dart';
-
-Future<void> main() async {
- deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(createFlutterGalleryTransitionsPerfSkSLWarmupTest());
-}
diff --git a/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart
index a525d30..c5add22 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:io';
-
import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
@@ -20,7 +18,12 @@
try {
await runProjectTest((FlutterProject project) async {
section('App bundle content for task bundleRelease without explicit target platform');
- await project.runGradleTask('bundleRelease');
+
+ await inDirectory(project.rootPath, () {
+ return flutter('build', options: <String>[
+ 'appbundle',
+ ]);
+ });
final String releaseBundle = path.join(
project.rootPath,
@@ -42,10 +45,6 @@
});
await runProjectTest((FlutterProject project) async {
- if (Platform.isWindows) {
- // https://github.com/flutter/flutter/issues/42985
- return;
- }
section('App bundle content using flavors without explicit target platform');
// Add a few flavors.
await project.addProductFlavors(<String> [
@@ -55,7 +54,13 @@
'flavor_underscore', // https://github.com/flutter/flutter/issues/36067
]);
// Build the production flavor in release mode.
- await project.runGradleTask('bundleProductionRelease');
+ await inDirectory(project.rootPath, () {
+ return flutter('build', options: <String>[
+ 'appbundle',
+ '--flavor',
+ 'production',
+ ]);
+ });
final String bundleFromGradlePath = path.join(
project.rootPath,
@@ -77,9 +82,8 @@
section('Build app bundle using the flutter tool - flavor: flavor_underscore');
- int exitCode;
- await inDirectory(project.rootPath, () async {
- exitCode = await flutter(
+ int exitCode = await inDirectory(project.rootPath, () {
+ return flutter(
'build',
options: <String>[
'appbundle',
@@ -113,8 +117,8 @@
section('Build app bundle using the flutter tool - flavor: production');
- await inDirectory(project.rootPath, () async {
- exitCode = await flutter(
+ exitCode = await inDirectory(project.rootPath, () {
+ return flutter(
'build',
options: <String>[
'appbundle',
@@ -149,8 +153,16 @@
await runProjectTest((FlutterProject project) async {
section('App bundle content for task bundleRelease with target platform = android-arm');
- await project.runGradleTask('bundleRelease',
- options: <String>['-Ptarget-platform=android-arm']);
+
+ await inDirectory(project.rootPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'appbundle',
+ '--target-platform=android-arm',
+ ],
+ );
+ });
final String releaseBundle = path.join(
project.rootPath,
diff --git a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart
index 634dd9f..af5f89f 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart
@@ -15,7 +15,15 @@
try {
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleDebug without explicit target platform');
- await pluginProject.runGradleTask('assembleDebug');
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--debug',
+ ],
+ );
+ });
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
@@ -40,7 +48,16 @@
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleRelease without explicit target platform');
- await pluginProject.runGradleTask('assembleRelease');
+
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--release',
+ ],
+ );
+ });
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.releaseApkPath);
@@ -60,8 +77,17 @@
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleRelease with target platform = android-arm, android-arm64');
- await pluginProject.runGradleTask('assembleRelease',
- options: <String>['-Ptarget-platform=android-arm,android-arm64']);
+
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--release',
+ '--target-platform=android-arm,android-arm64'
+ ],
+ );
+ });
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.releaseApkPath);
@@ -80,8 +106,18 @@
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleRelease with '
'target platform = android-arm, android-arm64 and split per ABI');
- await pluginProject.runGradleTask('assembleRelease',
- options: <String>['-Ptarget-platform=android-arm,android-arm64', '-Psplit-per-abi=true']);
+
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--release',
+ '--split-per-abi',
+ '--target-platform=android-arm,android-arm64',
+ ],
+ );
+ });
final Iterable<String> armApkFiles = await getFilesInApk(pluginProject.releaseArmApkPath);
@@ -108,7 +144,16 @@
await runProjectTest((FlutterProject project) async {
section('gradlew assembleRelease');
- await project.runGradleTask('assembleRelease');
+
+ await inDirectory(project.rootPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--release',
+ ],
+ );
+ });
// When the platform-target isn't specified, we generate the snapshots
// for arm and arm64.
diff --git a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
index 35280f7..f33e133 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
@@ -14,8 +14,17 @@
try {
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleDebug with target platform = android-arm');
- await pluginProject.runGradleTask('assembleDebug',
- options: <String>['-Ptarget-platform=android-arm']);
+
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--debug',
+ '--target-platform=android-arm'
+ ],
+ );
+ });
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
@@ -40,8 +49,16 @@
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleDebug with target platform = android-x86');
// This is used by `flutter run`
- await pluginProject.runGradleTask('assembleDebug',
- options: <String>['-Ptarget-platform=android-x86']);
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--debug',
+ '--target-platform=android-x86'
+ ],
+ );
+ });
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
@@ -64,8 +81,17 @@
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleDebug with target platform = android-x64');
// This is used by `flutter run`
- await pluginProject.runGradleTask('assembleDebug',
- options: <String>['-Ptarget-platform=android-x64']);
+
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--debug',
+ '--target-platform=android-x64'
+ ],
+ );
+ });
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
@@ -87,8 +113,17 @@
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleRelease with target platform = android-arm');
- await pluginProject.runGradleTask('assembleRelease',
- options: <String>['-Ptarget-platform=android-arm']);
+
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--release',
+ '--target-platform=android-arm'
+ ],
+ );
+ });
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.releaseApkPath);
@@ -108,8 +143,17 @@
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('APK content for task assembleRelease with target platform = android-arm64');
- await pluginProject.runGradleTask('assembleRelease',
- options: <String>['-Ptarget-platform=android-arm64']);
+
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--release',
+ '--target-platform=android-arm64'
+ ],
+ );
+ });
final Iterable<String> apkFiles = await getFilesInApk(pluginProject.releaseApkPath);
@@ -129,7 +173,15 @@
await runProjectTest((FlutterProject project) async {
section('gradlew assembleDebug');
- await project.runGradleTask('assembleDebug');
+ await inDirectory(project.rootPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--debug',
+ ],
+ );
+ });
final String errorMessage = validateSnapshotDependency(project, 'kernel_blob.bin');
if (errorMessage != null) {
throw TaskResult.failure(errorMessage);
@@ -138,7 +190,15 @@
await runProjectTest((FlutterProject project) async {
section('gradlew assembleProfile');
- await project.runGradleTask('assembleProfile');
+ await inDirectory(project.rootPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--profile',
+ ],
+ );
+ });
});
await runProjectTest((FlutterProject project) async {
@@ -173,8 +233,13 @@
await runProjectTest((FlutterProject project) async {
section('gradlew on build script with error');
await project.introduceError();
- final ProcessResult result =
- await project.resultOfGradleTask('assembleRelease');
+ final ProcessResult result = await inDirectory(project.rootPath, () {
+ return executeFlutter('build', options: <String>[
+ 'apk',
+ '--release',
+ ]);
+ });
+
if (result.exitCode == 0)
throw failure(
'Gradle did not exit with error as expected', result);
@@ -193,8 +258,12 @@
await runProjectTest((FlutterProject project) async {
section('gradlew assembleDebug forwards stderr');
await project.introducePubspecError();
- final ProcessResult result =
- await project.resultOfGradleTask('assembleRelease');
+ final ProcessResult result = await inDirectory(project.rootPath, () {
+ return executeFlutter('build', options: <String>[
+ 'apk',
+ '--release',
+ ]);
+ });
if (result.exitCode == 0)
throw failure(
'Gradle did not exit with error as expected', result);
@@ -206,7 +275,12 @@
await runProjectTest((FlutterProject project) async {
section('flutter build apk on build script with error');
await project.introduceError();
- final ProcessResult result = await project.resultOfFlutterCommand('build', <String>['apk']);
+ final ProcessResult result = await inDirectory(project.rootPath, () {
+ return executeFlutter('build', options: <String>[
+ 'apk',
+ '--release',
+ ]);
+ });
if (result.exitCode == 0)
throw failure(
'flutter build apk should fail when Gradle does', result);
@@ -223,7 +297,15 @@
await runPluginProjectTest((FlutterPluginProject pluginProject) async {
section('gradlew assembleDebug on plugin example');
- await pluginProject.runGradleTask('assembleDebug');
+ await inDirectory(pluginProject.exampleAndroidPath, () {
+ return flutter(
+ 'build',
+ options: <String>[
+ 'apk',
+ '--debug',
+ ],
+ );
+ });
if (!File(pluginProject.debugApkPath).existsSync())
throw TaskResult.failure(
'Gradle did not produce an apk file at the expected place');
diff --git a/dev/devicelab/bin/tasks/integration_ui.dart b/dev/devicelab/bin/tasks/integration_ui.dart
deleted file mode 100644
index 53f8e6d..0000000
--- a/dev/devicelab/bin/tasks/integration_ui.dart
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 'package:flutter_devicelab/tasks/integration_ui.dart';
-import 'package:flutter_devicelab/framework/adb.dart';
-import 'package:flutter_devicelab/framework/framework.dart';
-
-/// End to end tests for Android.
-Future<void> main() async {
- deviceOperatingSystem = DeviceOperatingSystem.android;
- await task(runEndToEndTests);
-}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_driver.dart
similarity index 78%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_driver.dart
index f585b54..91588c8 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_driver.dart
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
- deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(createFlutterDriverScreenshotTest());
+ deviceOperatingSystem = DeviceOperatingSystem.android;
+ await task(createEndToEndDriverTest());
}
diff --git a/dev/devicelab/bin/tasks/integration_ui_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios.dart
deleted file mode 100644
index b90310e..0000000
--- a/dev/devicelab/bin/tasks/integration_ui_ios.dart
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 'package:flutter_devicelab/tasks/integration_ui.dart';
-import 'package:flutter_devicelab/framework/adb.dart';
-import 'package:flutter_devicelab/framework/framework.dart';
-
-/// End to end tests for iOS.
-Future<void> main() async {
- deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(runEndToEndTests);
-}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios_driver.dart
similarity index 89%
rename from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
rename to dev/devicelab/bin/tasks/integration_ui_ios_driver.dart
index f585b54..af0d280 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_ios_driver.dart
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(createFlutterDriverScreenshotTest());
+ await task(createEndToEndDriverTest());
}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart
similarity index 89%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart
index f585b54..24152b8 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(createFlutterDriverScreenshotTest());
+ await task(createEndToEndKeyboardTest());
}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart
similarity index 89%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart
index f585b54..58c573a 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(createFlutterDriverScreenshotTest());
+ await task(createEndToEndScreenshotTest());
}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart
similarity index 88%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart
index f585b54..134dfb0 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(createFlutterDriverScreenshotTest());
+ await task(createEndToEndKeyboardTextfieldTest());
}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_keyboard_resize.dart
similarity index 78%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_keyboard_resize.dart
index f585b54..f2db416 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_keyboard_resize.dart
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
- deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(createFlutterDriverScreenshotTest());
+ deviceOperatingSystem = DeviceOperatingSystem.android;
+ await task(createEndToEndKeyboardTest());
}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart b/dev/devicelab/bin/tasks/integration_ui_screenshot.dart
similarity index 78%
copy from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
copy to dev/devicelab/bin/tasks/integration_ui_screenshot.dart
index f585b54..f3f9170 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_ios.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_screenshot.dart
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
- deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(createFlutterDriverScreenshotTest());
+ deviceOperatingSystem = DeviceOperatingSystem.android;
+ await task(createEndToEndScreenshotTest());
}
diff --git a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart b/dev/devicelab/bin/tasks/integration_ui_textfield.dart
similarity index 77%
rename from dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart
rename to dev/devicelab/bin/tasks/integration_ui_textfield.dart
index 1458908..228c134 100644
--- a/dev/devicelab/bin/tasks/flutter_driver_screenshot_test_fuchsia.dart
+++ b/dev/devicelab/bin/tasks/integration_ui_textfield.dart
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
- deviceOperatingSystem = DeviceOperatingSystem.fuchsia;
- await task(createFlutterDriverScreenshotTest());
+ deviceOperatingSystem = DeviceOperatingSystem.android;
+ await task(createEndToEndKeyboardTextfieldTest());
}
diff --git a/dev/devicelab/bin/tasks/ios_defines_test.dart b/dev/devicelab/bin/tasks/ios_defines_test.dart
index 79cb91d..5d73627 100644
--- a/dev/devicelab/bin/tasks/ios_defines_test.dart
+++ b/dev/devicelab/bin/tasks/ios_defines_test.dart
@@ -4,10 +4,10 @@
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
-import 'package:flutter_devicelab/tasks/defines_task.dart';
+import 'package:flutter_devicelab/tasks/integration_tests.dart';
/// Verify that dart defines work on iOS.
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios;
- await task(runDartDefinesTask);
+ await task(dartDefinesTask());
}
diff --git a/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart b/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart
index b1b6ad4..f26abcf 100644
--- a/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart
+++ b/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart
@@ -29,6 +29,11 @@
section('Create Flutter module project');
+ await flutter(
+ 'precache',
+ options: <String>['--android', '--no-ios'],
+ );
+
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
diff --git a/dev/devicelab/lib/framework/apk_utils.dart b/dev/devicelab/lib/framework/apk_utils.dart
index 18a8955..ae8db16 100644
--- a/dev/devicelab/lib/framework/apk_utils.dart
+++ b/dev/devicelab/lib/framework/apk_utils.dart
@@ -119,9 +119,23 @@
String workingDirectory,
}) async {
final String javaHome = await findJavaHome();
+
+ final String apkAnalyzer = path
+ .join(_androidHome, 'cmdline-tools', 'latest', 'bin', Platform.isWindows ? 'apkanalyzer.bat' : 'apkanalyzer');
+ if (canRun(apkAnalyzer)) {
+ return eval(
+ apkAnalyzer,
+ args,
+ printStdout: printStdout,
+ workingDirectory: workingDirectory,
+ environment: <String, String>{
+ 'JAVA_HOME': javaHome,
+ },
+ );
+ }
+
final String javaBinary = path.join(javaHome, 'bin', 'java');
assert(canRun(javaBinary));
-
final String androidTools = path.join(_androidHome, 'tools');
final String libs = path.join(androidTools, 'lib');
assert(Directory(libs).existsSync());
@@ -365,10 +379,6 @@
String get releaseArmApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'flutter-apk','app-armeabi-v7a-release.apk');
String get releaseArm64ApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'flutter-apk', 'app-arm64-v8a-release.apk');
String get releaseBundlePath => path.join(examplePath, 'build', 'app', 'outputs', 'bundle', 'release', 'app.aab');
-
- Future<void> runGradleTask(String task, {List<String> options}) async {
- return _runGradleTask(workingDirectory: exampleAndroidPath, task: task, options: options);
- }
}
class FlutterModuleProject {
diff --git a/dev/devicelab/lib/framework/cocoon.dart b/dev/devicelab/lib/framework/cocoon.dart
new file mode 100644
index 0000000..d1cecf2
--- /dev/null
+++ b/dev/devicelab/lib/framework/cocoon.dart
@@ -0,0 +1,152 @@
+// 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:async';
+import 'dart:convert' show json;
+import 'dart:io';
+
+import 'package:file/file.dart';
+import 'package:file/local.dart';
+import 'package:http/http.dart';
+import 'package:logging/logging.dart';
+import 'package:meta/meta.dart';
+
+import 'task_result.dart';
+import 'utils.dart';
+
+/// Class for test runner to interact with Flutter's infrastructure service, Cocoon.
+///
+/// Cocoon assigns bots to run these devicelab tasks on real devices.
+/// To retrieve these results, the test runner needs to send results back so so the database can be updated.
+class Cocoon {
+ Cocoon({
+ String serviceAccountTokenPath,
+ @visibleForTesting Client httpClient,
+ @visibleForTesting FileSystem filesystem,
+ }) : _httpClient = AuthenticatedCocoonClient(serviceAccountTokenPath, httpClient: httpClient, filesystem: filesystem);
+
+
+ /// Client to make http requests to Cocoon.
+ final AuthenticatedCocoonClient _httpClient;
+
+ /// Url used to send results to.
+ static const String baseCocoonApiUrl = 'https://flutter-dashboard.appspot.com/api';
+
+ static final Logger logger = Logger('CocoonClient');
+
+ String get commitSha => _commitSha ?? _readCommitSha();
+ String _commitSha;
+
+ /// Parse the local repo for the current running commit.
+ String _readCommitSha() {
+ final ProcessResult result = Process.runSync('git', <String>['rev-parse', 'HEAD']);
+ if (result.exitCode != 0) {
+ throw Exception(result.stderr);
+ }
+
+ _commitSha = result.stdout as String;
+ return _commitSha;
+ }
+
+ /// Send [TaskResult] to Cocoon.
+ Future<void> sendTaskResult({String taskName, TaskResult result}) async {
+ // Skip logging on test runs
+ Logger.root.level = Level.ALL;
+ Logger.root.onRecord.listen((LogRecord rec) {
+ print('${rec.level.name}: ${rec.time}: ${rec.message}');
+ });
+
+ final Map<String, dynamic> status = <String, dynamic>{
+ 'CommitSha': commitSha,
+ 'TaskName': taskName,
+ 'NewStatus': result.succeeded ? 'Succeeded' : 'Failed',
+ };
+
+ // Make a copy of result data because we may alter it for validation below.
+ status['ResultData'] = result.data;
+
+ final List<String> validScoreKeys = <String>[];
+ if (result.benchmarkScoreKeys != null) {
+ for (final String scoreKey in result.benchmarkScoreKeys) {
+ final Object score = result.data[scoreKey];
+ if (score is num) {
+ // Convert all metrics to double, which provide plenty of precision
+ // without having to add support for multiple numeric types in Cocoon.
+ result.data[scoreKey] = score.toDouble();
+ validScoreKeys.add(scoreKey);
+ }
+ }
+ }
+ status['BenchmarkScoreKeys'] = validScoreKeys;
+
+ final Map<String, dynamic> response = await _sendCocoonRequest('update-task-status', status);
+ if (response['Name'] != null) {
+ logger.info('Updated Cocoon with results from this task');
+ } else {
+ logger.info(response);
+ logger.severe('Failed to updated Cocoon with results from this task');
+ }
+ }
+
+ /// Make an API request to Cocoon.
+ Future<Map<String, dynamic>> _sendCocoonRequest(String apiPath, [dynamic jsonData]) async {
+ final String url = '$baseCocoonApiUrl/$apiPath';
+
+ /// Retry requests to Cocoon as sometimes there are issues with the servers, such
+ /// as version changes to the backend, datastore issues, or latency issues.
+ final Response response = await retry(
+ () => _httpClient.post(url, body: json.encode(jsonData)),
+ retryIf: (Exception e) => e is SocketException || e is TimeoutException || e is ClientException,
+ maxAttempts: 5,
+ );
+ return json.decode(response.body) as Map<String, dynamic>;
+ }
+}
+
+/// [HttpClient] for sending authenticated requests to Cocoon.
+class AuthenticatedCocoonClient extends BaseClient {
+ AuthenticatedCocoonClient(
+ this._serviceAccountTokenPath, {
+ @visibleForTesting Client httpClient,
+ @visibleForTesting FileSystem filesystem,
+ }) : _delegate = httpClient ?? Client(),
+ _fs = filesystem ?? const LocalFileSystem();
+
+ /// Authentication token to have the ability to upload and record test results.
+ ///
+ /// This is intended to only be passed on automated runs on LUCI post-submit.
+ final String _serviceAccountTokenPath;
+
+ /// Underlying [HttpClient] to send requests to.
+ final Client _delegate;
+
+ /// Underlying [FileSystem] to use.
+ final FileSystem _fs;
+
+ /// Value contained in the service account token file that can be used in http requests.
+ String get serviceAccountToken => _serviceAccountToken ?? _readServiceAccountTokenFile();
+ String _serviceAccountToken;
+
+ /// Get [serviceAccountToken] from the given service account file.
+ String _readServiceAccountTokenFile() {
+ return _serviceAccountToken = _fs.file(_serviceAccountTokenPath).readAsStringSync().trim();
+ }
+
+ @override
+ Future<StreamedResponse> send(BaseRequest request) async {
+ request.headers['Service-Account-Token'] = serviceAccountToken;
+ final StreamedResponse response = await _delegate.send(request);
+
+ if (response.statusCode != 200) {
+ throw ClientException(
+ 'AuthenticatedClientError:\n'
+ ' URI: ${request.url}\n'
+ ' HTTP Status: ${response.statusCode}\n'
+ ' Response body:\n'
+ '${(await Response.fromStream(response)).body}',
+ request.url);
+ }
+ return response;
+ }
+}
diff --git a/dev/devicelab/lib/framework/runner.dart b/dev/devicelab/lib/framework/runner.dart
index 5fa5295..6b7cd7a 100644
--- a/dev/devicelab/lib/framework/runner.dart
+++ b/dev/devicelab/lib/framework/runner.dart
@@ -6,7 +6,6 @@
import 'dart:convert';
import 'dart:io';
-import 'package:path/path.dart' as path;
import 'package:vm_service_client/vm_service_client.dart';
import 'package:flutter_devicelab/framework/utils.dart';
@@ -88,7 +87,6 @@
} finally {
if (!runnerFinished)
runner.kill(ProcessSignal.sigkill);
- await cleanupSystem();
await stdoutSub.cancel();
await stderrSub.cancel();
}
@@ -124,42 +122,3 @@
}
}
}
-
-Future<void> cleanupSystem() async {
- if (deviceOperatingSystem == null || deviceOperatingSystem == DeviceOperatingSystem.android) {
- print('\n\nCleaning up system after task...');
- final String javaHome = await findJavaHome();
- if (javaHome != null) {
- // To shut gradle down, we have to call "gradlew --stop".
- // To call gradlew, we need to have a gradle-wrapper.properties file along
- // with a shell script, a .jar file, etc. We get these from various places
- // as you see in the code below, and we save them all into a temporary dir
- // which we can then delete after.
- // All the steps below are somewhat tolerant of errors, because it doesn't
- // really matter if this succeeds every time or not.
- print('\nTelling Gradle to shut down (JAVA_HOME=$javaHome)');
- final String gradlewBinaryName = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
- final Directory tempDir = Directory.systemTemp.createTempSync('flutter_devicelab_shutdown_gradle.');
- recursiveCopy(Directory(path.join(flutterDirectory.path, 'bin', 'cache', 'artifacts', 'gradle_wrapper')), tempDir);
- copy(File(path.join(path.join(flutterDirectory.path, 'packages', 'flutter_tools'), 'templates', 'app', 'android.tmpl', 'gradle', 'wrapper', 'gradle-wrapper.properties')), Directory(path.join(tempDir.path, 'gradle', 'wrapper')));
- if (!Platform.isWindows) {
- await exec(
- 'chmod',
- <String>['a+x', path.join(tempDir.path, gradlewBinaryName)],
- canFail: true,
- );
- }
- await exec(
- path.join(tempDir.path, gradlewBinaryName),
- <String>['--stop'],
- environment: <String, String>{ 'JAVA_HOME': javaHome},
- workingDirectory: tempDir.path,
- canFail: true,
- );
- rmTree(tempDir);
- print('\n');
- } else {
- print('Could not determine JAVA_HOME; not shutting down Gradle.');
- }
- }
-}
diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart
index ad29d3f..097fb94 100644
--- a/dev/devicelab/lib/framework/utils.dart
+++ b/dev/devicelab/lib/framework/utils.dart
@@ -463,6 +463,16 @@
canFail: canFail, environment: environment, stderr: stderr);
}
+Future<ProcessResult> executeFlutter(String command, {
+ List<String> options = const <String>[],
+}) async {
+ final List<String> args = flutterCommandArgs(command, options);
+ return _processManager.run(
+ <String>[path.join(flutterDirectory.path, 'bin', 'flutter'), ...args],
+ workingDirectory: cwd,
+ );
+}
+
String get dartBin =>
path.join(flutterDirectory.path, 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
@@ -726,3 +736,34 @@
() => exec('git', <String>['clone', repo]),
);
}
+
+/// Call [fn] retrying so long as [retryIf] return `true` for the exception
+/// thrown and [maxAttempts] has not been reached.
+///
+/// If no [retryIf] function is given this will retry any for any [Exception]
+/// thrown. To retry on an [Error], the error must be caught and _rethrown_
+/// as an [Exception].
+///
+/// Waits a constant duration of [delayDuration] between every retry attempt.
+Future<T> retry<T>(
+ FutureOr<T> Function() fn, {
+ FutureOr<bool> Function(Exception) retryIf,
+ int maxAttempts = 5,
+ Duration delayDuration = const Duration(seconds: 3),
+}) async {
+ int attempt = 0;
+ while (true) {
+ attempt++; // first invocation is the first attempt
+ try {
+ return await fn();
+ } on Exception catch (e) {
+ if (attempt >= maxAttempts ||
+ (retryIf != null && !(await retryIf(e)))) {
+ rethrow;
+ }
+ }
+
+ // Sleep for a delay
+ await Future<void>.delayed(delayDuration);
+ }
+}
diff --git a/dev/devicelab/lib/tasks/defines_task.dart b/dev/devicelab/lib/tasks/defines_task.dart
deleted file mode 100644
index 044542f..0000000
--- a/dev/devicelab/lib/tasks/defines_task.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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:io';
-
-import '../framework/adb.dart';
-import '../framework/task_result.dart';
-import '../framework/utils.dart';
-
-Future<TaskResult> runDartDefinesTask() async {
- final Device device = await devices.workingDevice;
- await device.unlock();
- final String deviceId = device.deviceId;
- final Directory testDirectory = dir('${flutterDirectory.path}/dev/integration_tests/ui');
- await inDirectory<void>(testDirectory, () async {
- await flutter('packages', options: <String>['get']);
-
- await flutter('drive', options: <String>[
- '--verbose',
- '-d',
- deviceId,
- '--dart-define=test.valueA=Example',
- '--dart-define=test.valueB=Value',
- 'lib/defines.dart',
- ]);
- });
-
- return TaskResult.success(<String, dynamic>{});
-}
diff --git a/dev/devicelab/lib/tasks/gallery.dart b/dev/devicelab/lib/tasks/gallery.dart
index b06beae..66b4ae0 100644
--- a/dev/devicelab/lib/tasks/gallery.dart
+++ b/dev/devicelab/lib/tasks/gallery.dart
@@ -71,6 +71,7 @@
'build',
options: <String>[
'apk',
+ '--no-android-gradle-daemon',
'--profile',
'-t',
'test_driver/$testFile.dart',
diff --git a/dev/devicelab/lib/tasks/hot_mode_tests.dart b/dev/devicelab/lib/tasks/hot_mode_tests.dart
index c5b76c0..fc3f721 100644
--- a/dev/devicelab/lib/tasks/hot_mode_tests.dart
+++ b/dev/devicelab/lib/tasks/hot_mode_tests.dart
@@ -26,7 +26,7 @@
final File benchmarkFile = file(path.join(_editedFlutterGalleryDir.path, 'hot_benchmark.json'));
rm(benchmarkFile);
final List<String> options = <String>[
- '--hot', '-d', deviceIdOverride, '--benchmark', '--verbose', '--resident',
+ '--hot', '-d', deviceIdOverride, '--benchmark', '--verbose', '--resident', '--no-android-gradle-daemon',
];
int hotReloadCount = 0;
Map<String, dynamic> twoReloadsData;
diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart
index bd0d087..30a5b4c 100644
--- a/dev/devicelab/lib/tasks/integration_tests.dart
+++ b/dev/devicelab/lib/tasks/integration_tests.dart
@@ -92,15 +92,6 @@
);
}
-/// Executes a driver test that takes a screenshot and compares it against a golden image.
-/// The golden image is served by Flutter Gold (https://flutter-gold.skia.org/).
-TaskFunction createFlutterDriverScreenshotTest() {
- return DriverTest(
- '${flutterDirectory.path}/dev/integration_tests/flutter_driver_screenshot_test',
- 'lib/main.dart',
- );
-}
-
TaskFunction createIOSPlatformViewTests() {
return DriverTest(
'${flutterDirectory.path}/dev/integration_tests/ios_platform_view_tests',
@@ -108,6 +99,44 @@
);
}
+TaskFunction createEndToEndKeyboardTest() {
+ return DriverTest(
+ '${flutterDirectory.path}/dev/integration_tests/ui',
+ 'lib/keyboard_resize.dart',
+ );
+}
+
+TaskFunction createEndToEndDriverTest() {
+ return DriverTest(
+ '${flutterDirectory.path}/dev/integration_tests/ui',
+ 'lib/driver.dart',
+ );
+}
+
+TaskFunction createEndToEndScreenshotTest() {
+ return DriverTest(
+ '${flutterDirectory.path}/dev/integration_tests/ui',
+ 'lib/screenshot.dart',
+ );
+}
+
+TaskFunction createEndToEndKeyboardTextfieldTest() {
+ return DriverTest(
+ '${flutterDirectory.path}/dev/integration_tests/ui',
+ 'lib/keyboard_textfield.dart',
+ );
+}
+
+TaskFunction dartDefinesTask() {
+ return DriverTest(
+ '${flutterDirectory.path}/dev/integration_tests/ui',
+ 'lib/defines.dart', extraOptions: <String>[
+ '--dart-define=test.valueA=Example',
+ '--dart-define=test.valueB=Value',
+ ],
+ );
+}
+
class DriverTest {
DriverTest(
this.testDirectory,
@@ -130,6 +159,7 @@
await flutter('packages', options: <String>['get']);
final List<String> options = <String>[
+ '--no-android-gradle-daemon',
'-v',
'-t',
testTarget,
diff --git a/dev/devicelab/lib/tasks/integration_ui.dart b/dev/devicelab/lib/tasks/integration_ui.dart
deleted file mode 100644
index 6223e0b..0000000
--- a/dev/devicelab/lib/tasks/integration_ui.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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:io';
-
-import '../framework/adb.dart';
-import '../framework/task_result.dart';
-import '../framework/utils.dart';
-
-Future<TaskResult> runEndToEndTests() async {
- final Device device = await devices.workingDevice;
- await device.unlock();
- final String deviceId = device.deviceId;
- final Directory testDirectory = dir('${flutterDirectory.path}/dev/integration_tests/ui');
- await inDirectory<void>(testDirectory, () async {
- await flutter('packages', options: <String>['get']);
-
- const List<String> entryPoints = <String>[
- 'lib/keyboard_resize.dart',
- 'lib/driver.dart',
- 'lib/screenshot.dart',
- 'lib/keyboard_textfield.dart',
- ];
-
- for (final String entryPoint in entryPoints) {
- await flutter('drive', options: <String>['--verbose', '-d', deviceId, entryPoint]);
- }
- });
-
- return TaskResult.success(<String, dynamic>{});
-}
diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart
index bb8e19d..e3d735a 100644
--- a/dev/devicelab/lib/tasks/perf_tests.dart
+++ b/dev/devicelab/lib/tasks/perf_tests.dart
@@ -347,6 +347,7 @@
await flutter('packages', options: <String>['get']);
await flutter('drive', options: <String>[
+ '--no-android-gradle-daemon',
'-v',
'--verbose-system-logs',
'--profile',
@@ -395,6 +396,7 @@
await flutter('packages', options: <String>['get']);
await flutter('drive', options: <String>[
+ '--no-android-gradle-daemon',
'-v',
'--verbose-system-logs',
'--profile',
@@ -452,23 +454,56 @@
Future<TaskResult> run() async {
return await inDirectory<TaskResult>(testDirectory, () async {
- final String deviceId = (await devices.workingDevice).deviceId;
- await flutter('packages', options: <String>['get']);
-
- const int iterations = 15;
+ final Device device = await devices.workingDevice;
+ const int iterations = 5;
final List<Map<String, dynamic>> results = <Map<String, dynamic>>[];
- for (int i = 0; i < iterations; ++i) {
+
+ section('Building application');
+ String applicationBinaryPath;
+ switch (deviceOperatingSystem) {
+ case DeviceOperatingSystem.android:
+ await flutter('build', options: <String>[
+ 'apk',
+ '-v',
+ '--profile',
+ '--target-platform=android-arm,android-arm64',
+ ]);
+ applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
+ break;
+ case DeviceOperatingSystem.ios:
+ await flutter('build', options: <String>[
+ 'ios',
+ '-v',
+ '--profile',
+ ]);
+ applicationBinaryPath = _findIosAppInBuildDirectory('$testDirectory/build/ios/iphoneos');
+ break;
+ case DeviceOperatingSystem.fuchsia:
+ case DeviceOperatingSystem.fake:
+ break;
+ }
+
+ for (int i = 0; i < iterations; i += 1) {
await flutter('run', options: <String>[
+ '--no-android-gradle-daemon',
'--verbose',
'--profile',
'--trace-startup',
'-d',
- deviceId,
+ device.deviceId,
+ if (applicationBinaryPath != null)
+ '--use-application-binary=$applicationBinaryPath',
]);
final Map<String, dynamic> data = json.decode(
file('$testDirectory/build/start_up_info.json').readAsStringSync(),
) as Map<String, dynamic>;
results.add(data);
+
+ await flutter('install', options: <String>[
+ '--uninstall-only',
+ '-d',
+ device.deviceId,
+ ]);
}
final Map<String, dynamic> averageResults = _average(results, iterations);
@@ -575,6 +610,7 @@
await flutter('packages', options: <String>['get']);
await flutter('drive', options: <String>[
+ '--no-android-gradle-daemon',
'-v',
'--verbose-system-logs',
'--profile',
@@ -1090,6 +1126,7 @@
}
await adb.cancel();
+ await flutter('install', options: <String>['--uninstall-only', '-d', device.deviceId]);
final ListStatistics startMemoryStatistics = ListStatistics(_startMemory);
final ListStatistics endMemoryStatistics = ListStatistics(_endMemory);
@@ -1424,3 +1461,12 @@
final int compressedSize;
final String path;
}
+
+String _findIosAppInBuildDirectory(String searchDirectory) {
+ for (final FileSystemEntity entity in Directory(searchDirectory).listSync()) {
+ if (entity.path.endsWith('.app')) {
+ return entity.path;
+ }
+ }
+ return null;
+}
diff --git a/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart b/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart
index 620dda6..04ab69e 100644
--- a/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart
+++ b/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart
@@ -43,6 +43,7 @@
final Process runProcess = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'),
flutterCommandArgs('run', <String>[
+ '--no-android-gradle-daemon',
...?additionalArgs,
'--vmservice-out-file=info',
'--track-widget-creation',
@@ -78,6 +79,7 @@
final Process runProcess = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'),
flutterCommandArgs('run', <String>[
+ '--no-android-gradle-daemon',
...?additionalArgs,
'--vmservice-out-file=info',
'--no-track-widget-creation',
diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml
index 2bec4d8..5a08535 100644
--- a/dev/devicelab/manifest.yaml
+++ b/dev/devicelab/manifest.yaml
@@ -229,14 +229,6 @@
stage: devicelab
required_agent_capabilities: ["linux/android"]
- flutter_gallery_sksl_warmup_ios32__transition_perf:
- description: >
- Measures the runtime performance of Flutter gallery transitions on iPhone4s
- with SkSL shader warm-up.
- stage: devicelab_ios
- required_agent_capabilities: ["mac/ios32"]
- flaky: true
-
flutter_gallery_sksl_warmup__transition_perf_e2e_ios32:
description: >
Measures the runtime performance of Flutter gallery transitions on iPhone4s
@@ -401,17 +393,29 @@
stage: devicelab
required_agent_capabilities: ["mac/android"]
- integration_ui:
+ integration_ui_driver:
description: >
Runs end-to-end Flutter tests on Android.
stage: devicelab
required_agent_capabilities: ["mac/android"]
- commands_test:
+ integration_ui_keyboard_resize:
description: >
- Runs tests of flutter run commands.
+ Runs end-to-end Flutter tests on Android.
stage: devicelab
- required_agent_capabilities: ["mac/android"]
+ required_agent_capabilities: [ "mac/android" ]
+
+ integration_ui_screenshot:
+ description: >
+ Runs end-to-end Flutter tests on Android.
+ stage: devicelab
+ required_agent_capabilities: [ "mac/android" ]
+
+ integration_ui_textfield:
+ description: >
+ Runs end-to-end Flutter tests on Android.
+ stage: devicelab
+ required_agent_capabilities: [ "mac/android" ]
service_extensions_test:
description: >
@@ -608,7 +612,25 @@
stage: devicelab_ios
required_agent_capabilities: ["mac/ios"]
- integration_ui_ios:
+ integration_ui_ios_driver:
+ description: >
+ Runs end-to-end Flutter tests on iPhone 6.
+ stage: devicelab_ios
+ required_agent_capabilities: ["mac/ios"]
+
+ integration_ui_ios_keyboard_resize:
+ description: >
+ Runs end-to-end Flutter tests on iPhone 6.
+ stage: devicelab_ios
+ required_agent_capabilities: ["mac/ios"]
+
+ integration_ui_ios_screenshot:
+ description: >
+ Runs end-to-end Flutter tests on iPhone 6.
+ stage: devicelab_ios
+ required_agent_capabilities: ["mac/ios"]
+
+ integration_ui_ios_textfield:
description: >
Runs end-to-end Flutter tests on iPhone 6.
stage: devicelab_ios
@@ -899,16 +921,6 @@
# required_agent_capabilities: ["linux/android"]
# flaky: true
- # TODO(cyanglaz): Enable this test when we know how to test gold in device labs
- # Or completely remove this test to LUCI when LUCI supports physical iOS devices.
- # flutter_driver_screenshot_test_ios:
- # description: >
- # Screenshot tests running on a specifc iPhone 6.
- # The test makes sure that there is no regression while renderring an image with gl on iOS.
- # stage: devicelab_ios
- # required_agent_capabilities: ["mac/ios", "ios/gl-render-image"]
- # flaky: true
-
flutter_gallery_v2_chrome_run_test:
description: >
Checks that the New Flutter Gallery runs successfully on Chrome.
diff --git a/dev/devicelab/test/cocoon_test.dart b/dev/devicelab/test/cocoon_test.dart
new file mode 100644
index 0000000..f5c27d8
--- /dev/null
+++ b/dev/devicelab/test/cocoon_test.dart
@@ -0,0 +1,88 @@
+// 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 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:http/http.dart';
+import 'package:http/testing.dart';
+
+import 'package:flutter_devicelab/framework/cocoon.dart';
+import 'package:flutter_devicelab/framework/task_result.dart';
+
+import 'common.dart';
+
+void main() {
+ group('Cocoon', () {
+ const String serviceAccountTokenPath = 'test_account_file';
+ const String serviceAccountToken = 'test_token';
+
+ Client mockClient;
+ Cocoon cocoon;
+ FileSystem fs;
+
+ setUp(() {
+ fs = MemoryFileSystem();
+
+ final File serviceAccountFile = fs.file(serviceAccountTokenPath)..createSync();
+ serviceAccountFile.writeAsStringSync(serviceAccountToken);
+ });
+
+ test('sends expected request from successful task', () async {
+ mockClient = MockClient((Request request) async => Response('{}', 200));
+
+ cocoon = Cocoon(
+ serviceAccountTokenPath: serviceAccountTokenPath,
+ filesystem: fs,
+ httpClient: mockClient,
+ );
+
+ final TaskResult result = TaskResult.success(<String, dynamic>{});
+ // This should not throw an error.
+ await cocoon.sendTaskResult(taskName: 'taskAbc', result: result);
+ });
+
+ test('throws client exception on non-200 responses', () async {
+ mockClient = MockClient((Request request) async => Response('', 500));
+
+ cocoon = Cocoon(
+ serviceAccountTokenPath: serviceAccountTokenPath,
+ filesystem: fs,
+ httpClient: mockClient,
+ );
+
+ final TaskResult result = TaskResult.success(<String, dynamic>{});
+ expect(() => cocoon.sendTaskResult(taskName: 'taskAbc', result: result), throwsA(isA<ClientException>()));
+ });
+ });
+
+ group('AuthenticatedCocoonClient', () {
+ const String serviceAccountPath = 'test_account_file';
+ const String serviceAccountToken = 'test_token';
+
+ FileSystem fs;
+
+ setUp(() {
+ fs = MemoryFileSystem();
+ final File serviceAccountFile = fs.file(serviceAccountPath)..createSync();
+ serviceAccountFile.writeAsStringSync(serviceAccountToken);
+ });
+
+ test('reads token from service account file', () {
+ final AuthenticatedCocoonClient client = AuthenticatedCocoonClient(serviceAccountPath, filesystem: fs);
+ expect(client.serviceAccountToken, serviceAccountToken);
+ });
+
+ test('reads token from service account file with whitespace', () {
+ final File serviceAccountFile = fs.file(serviceAccountPath)..createSync();
+ serviceAccountFile.writeAsStringSync(serviceAccountToken + ' \n');
+ final AuthenticatedCocoonClient client = AuthenticatedCocoonClient(serviceAccountPath, filesystem: fs);
+ expect(client.serviceAccountToken, serviceAccountToken);
+ });
+
+ test('throws error when service account file not found', () {
+ final AuthenticatedCocoonClient client = AuthenticatedCocoonClient('idontexist', filesystem: fs);
+ expect(() => client.serviceAccountToken, throwsA(isA<FileSystemException>()));
+ });
+ });
+}
diff --git a/dev/devicelab/test/run_test.dart b/dev/devicelab/test/run_test.dart
index ec5110e..0788659 100644
--- a/dev/devicelab/test/run_test.dart
+++ b/dev/devicelab/test/run_test.dart
@@ -138,5 +138,14 @@
),
);
});
+
+ test('fails to upload results to Cocoon if flags given', () async {
+ // CocoonClient will fail to find test-file, and will not send any http requests.
+ final ProcessResult result = await runScript(
+ <String>['smoke_test_success'],
+ <String>['--service-account-file=test-file', '--task-key=task123'],
+ );
+ expect(result.exitCode, 1);
+ });
});
}
diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml
index 5cdffdf..275937d 100644
--- a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml
+++ b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml
@@ -20,12 +20,12 @@
flutter:
sdk: flutter
# This plugin is using Android Embedding 1
- battery: 1.0.6
+ battery: 1.0.7
# TODO(egarciad): Add a plugin that uses Android Embedding 2
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: 0.1.3
+ cupertino_icons: 1.0.0
battery_platform_interface: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -96,4 +96,4 @@
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
-# PUBSPEC CHECKSUM: bf4a
+# PUBSPEC CHECKSUM: 3f48
diff --git a/dev/integration_tests/android_semantics_testing/pubspec.yaml b/dev/integration_tests/android_semantics_testing/pubspec.yaml
index f31ee94..77d4867 100644
--- a/dev/integration_tests/android_semantics_testing/pubspec.yaml
+++ b/dev/integration_tests/android_semantics_testing/pubspec.yaml
@@ -1,5 +1,7 @@
name: android_semantics_testing
description: Integration testing library for Android semantics
+environment:
+ sdk: '>=2.9.0 <3.0.0'
dependencies:
flutter:
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml
index 675d1d5..fb4406f 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml
+++ b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml
@@ -22,7 +22,7 @@
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: 0.1.3
+ cupertino_icons: 1.0.0
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -137,4 +137,4 @@
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
-# PUBSPEC CHECKSUM: eba8
+# PUBSPEC CHECKSUM: 20a5
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/pubspec.yaml
index fe5489f..08586f3 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/pubspec.yaml
+++ b/dev/integration_tests/android_splash_screens/splash_screen_load_rotate/pubspec.yaml
@@ -22,7 +22,7 @@
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: 0.1.3
+ cupertino_icons: 1.0.0
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -91,4 +91,4 @@
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml
index 2ed07f8..1d19050 100644
--- a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml
+++ b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml
@@ -22,7 +22,7 @@
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: 0.1.3
+ cupertino_icons: 1.0.0
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -137,4 +137,4 @@
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
-# PUBSPEC CHECKSUM: eba8
+# PUBSPEC CHECKSUM: 20a5
diff --git a/dev/integration_tests/android_views/pubspec.yaml b/dev/integration_tests/android_views/pubspec.yaml
index 127ab8e..4666377 100644
--- a/dev/integration_tests/android_views/pubspec.yaml
+++ b/dev/integration_tests/android_views/pubspec.yaml
@@ -1,6 +1,8 @@
name: platform_views
description: An integration test for embedded platform views
version: 1.0.0+1
+environment:
+ sdk: '>=2.9.0 <3.0.0'
dependencies:
flutter:
diff --git a/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml b/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml
index 29987d4..0d2c544 100644
--- a/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml
+++ b/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml
@@ -9,8 +9,8 @@
sdk: flutter
flutter_driver:
sdk: flutter
- cupertino_icons: 0.1.3
- device_info: 0.4.2+8
+ cupertino_icons: 1.0.0
+ device_info: 0.4.2+9
archive: 2.0.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
args: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -82,4 +82,4 @@
assets:
- assets/
-# PUBSPEC CHECKSUM: 5acc
+# PUBSPEC CHECKSUM: beca
diff --git a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
index 097d7d5..36fbb4d 100644
--- a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
+++ b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
@@ -32,18 +32,19 @@
const int _kChildCount = 50;
class CupertinoNavigationDemo extends StatelessWidget {
- CupertinoNavigationDemo()
+ CupertinoNavigationDemo({ this.randomSeed })
: colorItems = List<Color>.generate(_kChildCount, (int index) {
- return coolColors[math.Random().nextInt(coolColors.length)];
+ return coolColors[math.Random(randomSeed).nextInt(coolColors.length)];
}) ,
colorNameItems = List<String>.generate(_kChildCount, (int index) {
- return coolColorNames[math.Random().nextInt(coolColorNames.length)];
+ return coolColorNames[math.Random(randomSeed).nextInt(coolColorNames.length)];
});
static const String routeName = '/cupertino/navigation';
final List<Color> colorItems;
final List<String> colorNameItems;
+ final int randomSeed;
@override
Widget build(BuildContext context) {
@@ -56,15 +57,15 @@
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
- icon: Icon(CupertinoIcons.home),
+ icon: Icon(CupertinoIcons.house, size: 27),
label: 'Home',
),
BottomNavigationBarItem(
- icon: Icon(CupertinoIcons.conversation_bubble),
+ icon: Icon(CupertinoIcons.chat_bubble, size: 27),
label: 'Support',
),
BottomNavigationBarItem(
- icon: Icon(CupertinoIcons.profile_circled),
+ icon: Icon(CupertinoIcons.person_circle, size: 27),
label: 'Profile',
),
],
@@ -78,6 +79,7 @@
return CupertinoDemoTab1(
colorItems: colorItems,
colorNameItems: colorNameItems,
+ randomSeed: randomSeed,
);
},
defaultTitle: 'Colors',
@@ -134,10 +136,15 @@
);
class CupertinoDemoTab1 extends StatelessWidget {
- const CupertinoDemoTab1({this.colorItems, this.colorNameItems});
+ const CupertinoDemoTab1({
+ this.colorItems,
+ this.colorNameItems,
+ this.randomSeed,
+ });
final List<Color> colorItems;
final List<String> colorNameItems;
+ final int randomSeed;
@override
Widget build(BuildContext context) {
@@ -165,6 +172,7 @@
lastItem: index == _kChildCount - 1,
color: colorItems[index],
colorName: colorNameItems[index],
+ randomSeed: randomSeed,
);
},
childCount: _kChildCount,
@@ -178,12 +186,19 @@
}
class Tab1RowItem extends StatelessWidget {
- const Tab1RowItem({this.index, this.lastItem, this.color, this.colorName});
+ const Tab1RowItem({
+ this.index,
+ this.lastItem,
+ this.color,
+ this.colorName,
+ this.randomSeed,
+ });
final int index;
final bool lastItem;
final Color color;
final String colorName;
+ final int randomSeed;
@override
Widget build(BuildContext context) {
@@ -196,6 +211,7 @@
color: color,
colorName: colorName,
index: index,
+ randomSeed: randomSeed,
),
));
},
@@ -274,11 +290,12 @@
}
class Tab1ItemPage extends StatefulWidget {
- const Tab1ItemPage({this.color, this.colorName, this.index});
+ const Tab1ItemPage({this.color, this.colorName, this.index, this.randomSeed});
final Color color;
final String colorName;
final int index;
+ final int randomSeed;
@override
State<StatefulWidget> createState() => Tab1ItemPageState();
@@ -289,7 +306,7 @@
void initState() {
super.initState();
relatedColors = List<Color>.generate(10, (int index) {
- final math.Random random = math.Random();
+ final math.Random random = math.Random(widget.randomSeed);
return Color.fromARGB(
255,
(widget.color.red + random.nextInt(100) - 50).clamp(0, 255) as int,
diff --git a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
index 08964d6..77956dd 100644
--- a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
+++ b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
@@ -40,17 +40,15 @@
keyboardType: TextInputType.multiline,
prefix: const Padding(padding: EdgeInsets.symmetric(horizontal: 4.0)),
suffix: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 4.0),
+ padding: const EdgeInsets.symmetric(horizontal: 2.0),
child: CupertinoButton(
- color: CupertinoColors.activeGreen,
minSize: 0.0,
child: const Icon(
- CupertinoIcons.up_arrow,
- size: 21.0,
- color: CupertinoColors.white,
+ CupertinoIcons.arrow_up_circle_fill,
+ size: 28.0,
+ color: CupertinoColors.activeGreen,
),
- padding: const EdgeInsets.all(2.0),
- borderRadius: BorderRadius.circular(15.0),
+ padding: const EdgeInsets.only(bottom: 4),
onPressed: ()=> setState(()=> _chatTextController.clear()),
),
),
@@ -63,7 +61,7 @@
Widget _buildNameField() {
return const CupertinoTextField(
prefix: Icon(
- CupertinoIcons.person_solid,
+ CupertinoIcons.person_fill,
color: CupertinoColors.lightBackgroundGray,
size: 28.0,
),
@@ -81,9 +79,9 @@
Widget _buildEmailField() {
return const CupertinoTextField(
prefix: Icon(
- CupertinoIcons.mail_solid,
+ CupertinoIcons.envelope_fill,
color: CupertinoColors.lightBackgroundGray,
- size: 28.0,
+ size: 26,
),
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
clearButtonMode: OverlayVisibilityMode.editing,
@@ -100,9 +98,9 @@
return CupertinoTextField(
controller: _locationTextController,
prefix: const Icon(
- CupertinoIcons.location_solid,
+ CupertinoIcons.location_fill,
color: CupertinoColors.lightBackgroundGray,
- size: 28.0,
+ size: 26,
),
padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
clearButtonMode: OverlayVisibilityMode.editing,
@@ -117,9 +115,9 @@
Widget _buildPinField() {
return const CupertinoTextField(
prefix: Icon(
- CupertinoIcons.padlock_solid,
+ CupertinoIcons.lock_open_fill,
color: CupertinoColors.lightBackgroundGray,
- size: 28.0,
+ size: 26,
),
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
clearButtonMode: OverlayVisibilityMode.editing,
@@ -137,9 +135,9 @@
return CupertinoTextField(
controller: TextEditingController(text: 'colleague, reading club'),
prefix: const Icon(
- CupertinoIcons.tags_solid,
+ CupertinoIcons.tag_fill,
color: CupertinoColors.lightBackgroundGray,
- size: 28.0,
+ size: 26,
),
enabled: false,
padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
diff --git a/dev/integration_tests/flutter_gallery/pubspec.yaml b/dev/integration_tests/flutter_gallery/pubspec.yaml
index b0cba85..8ea5749 100644
--- a/dev/integration_tests/flutter_gallery/pubspec.yaml
+++ b/dev/integration_tests/flutter_gallery/pubspec.yaml
@@ -8,12 +8,12 @@
flutter:
sdk: flutter
collection: 1.15.0-nullsafety.3
- device_info: 0.4.2+8
+ device_info: 0.4.2+9
intl: 0.16.1
- connectivity: 0.4.9+3
+ connectivity: 0.4.9+5
string_scanner: 1.1.0-nullsafety.1
- url_launcher: 5.7.2
- cupertino_icons: 0.1.3
+ url_launcher: 5.7.5
+ cupertino_icons: 1.0.0
video_player: 0.10.6
scoped_model: 1.0.1
shrine_images: 1.1.2
@@ -52,7 +52,7 @@
flutter_goldens:
sdk: flutter
test: 1.16.0-nullsafety.5
- e2e: 0.7.0+1
+ integration_test: 0.9.2+1
_fe_analyzer_shared: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
analyzer: 0.39.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -277,4 +277,4 @@
- asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf
- asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf
-# PUBSPEC CHECKSUM: 023a
+# PUBSPEC CHECKSUM: c70e
diff --git a/dev/integration_tests/flutter_gallery/test/demo/cupertino/cupertino_navigation_demo_test.dart b/dev/integration_tests/flutter_gallery/test/demo/cupertino/cupertino_navigation_demo_test.dart
new file mode 100644
index 0000000..e1698fb
--- /dev/null
+++ b/dev/integration_tests/flutter_gallery/test/demo/cupertino/cupertino_navigation_demo_test.dart
@@ -0,0 +1,41 @@
+// 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 'package:flutter/cupertino.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_gallery/demo/cupertino/cupertino_navigation_demo.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ testWidgets('Navigation demo golden', (WidgetTester tester) async {
+ // The point is to mainly test the cupertino icons that we don't have a
+ // dependency against in the flutter/cupertino package directly.
+
+ final Future<ByteData> font = rootBundle.load(
+ 'packages/cupertino_icons/assets/CupertinoIcons.ttf'
+ );
+
+ await (FontLoader('packages/cupertino_icons/CupertinoIcons')..addFont(font))
+ .load();
+
+ await tester.pumpWidget(CupertinoApp(
+ home: CupertinoNavigationDemo(randomSeed: 123456),
+ ));
+
+ await expectLater(
+ find.byType(CupertinoNavigationDemo),
+ matchesGoldenFile('cupertino_navigation_demo.screen.1.png'),
+ );
+
+ // Tap some row to go to the next page.
+ await tester.tap(find.text('Buy this cool color').first);
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 500));
+
+ await expectLater(
+ find.byType(CupertinoNavigationDemo),
+ matchesGoldenFile('cupertino_navigation_demo.screen.2.png'),
+ );
+ });
+}
diff --git a/dev/integration_tests/flutter_gallery/test_driver/e2e_utils.dart b/dev/integration_tests/flutter_gallery/test_driver/e2e_utils.dart
deleted file mode 100644
index 813dae3..0000000
--- a/dev/integration_tests/flutter_gallery/test_driver/e2e_utils.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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/scheduler.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'package:e2e/e2e.dart';
-
-bool _firstRun = true;
-
-// TODO(CareF): move this to e2e after FrameTimingSummarizer goes into stable
-// branch (#63537)
-/// watches the [FrameTiming] of `action` and report it to the e2e binding.
-Future<void> watchPerformance(
- E2EWidgetsFlutterBinding binding,
- Future<void> action(), {
- String reportKey = 'performance',
-}) async {
- assert(() {
- if (_firstRun) {
- debugPrint(kDebugWarning);
- _firstRun = false;
- }
- return true;
- }());
-
- // The engine could batch FrameTimings and send them only once per second.
- // Delay for a sufficient time so either old FrameTimings are flushed and not
- // interfering our measurements here, or new FrameTimings are all reported.
- Future<void> delayForFrameTimings() =>
- Future<void>.delayed(const Duration(seconds: 2));
-
- await delayForFrameTimings(); // flush old FrameTimings
- final List<FrameTiming> frameTimings = <FrameTiming>[];
- final TimingsCallback watcher = frameTimings.addAll;
- binding.addTimingsCallback(watcher);
- await action();
- await delayForFrameTimings(); // make sure all FrameTimings are reported
- binding.removeTimingsCallback(watcher);
- final FrameTimingSummarizer frameTimes = FrameTimingSummarizer(frameTimings);
- binding.reportData = <String, dynamic>{reportKey: frameTimes.summary};
-}
diff --git a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e.dart b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e.dart
index 3ebe0e4..69e9be5 100644
--- a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e.dart
+++ b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e.dart
@@ -6,13 +6,12 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:e2e/e2e.dart';
+import 'package:integration_test/integration_test.dart';
import 'package:flutter_gallery/gallery/app.dart' show GalleryApp;
import 'package:flutter_gallery/gallery/demos.dart';
import 'package:flutter_gallery/demo_lists.dart';
-import 'e2e_utils.dart';
import 'run_demos.dart';
const List<String> kSkippedDemos = <String>[];
@@ -27,8 +26,8 @@
void main([List<String> args = const <String>[]]) {
final bool withSemantics = args.contains('--with_semantics');
- final E2EWidgetsFlutterBinding binding =
- E2EWidgetsFlutterBinding.ensureInitialized() as E2EWidgetsFlutterBinding;
+ final IntegrationTestWidgetsFlutterBinding binding =
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding;
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
group('flutter gallery transitions on e2e', () {
testWidgets('find.bySemanticsLabel', (WidgetTester tester) async {
@@ -44,7 +43,7 @@
runApp(const GalleryApp(testMode: true));
await tester.pumpAndSettle();
// Collect timeline data for just a limited set of demos to avoid OOMs.
- await watchPerformance(binding, () async {
+ await binding.watchPerformance(() async {
await runDemos(kProfiledDemos, tester);
});
diff --git a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e_test.dart b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e_test.dart
index a7d388e..d931eff 100644
--- a/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e_test.dart
+++ b/dev/integration_tests/flutter_gallery/test_driver/transitions_perf_e2e_test.dart
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:e2e/e2e_driver.dart' as driver;
+import 'package:integration_test/integration_test_driver.dart' as driver;
-Future<void> main() => driver.e2eDriver(
+Future<void> main() => driver.integrationDriver(
timeout: const Duration(minutes: 5),
responseDataCallback: (Map<String, dynamic> data) async {
await driver.writeResponseData(
diff --git a/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml b/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml
index 0f78772..f46ce71 100644
--- a/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml
+++ b/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml
@@ -8,7 +8,7 @@
dependencies:
flutter:
sdk: flutter
- camera: 0.5.8+8
+ camera: 0.5.8+9
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -19,4 +19,4 @@
flutter:
uses-material-design: true
-# PUBSPEC CHECKSUM: 8dce
+# PUBSPEC CHECKSUM: b3cf
diff --git a/dev/integration_tests/hybrid_android_views/pubspec.yaml b/dev/integration_tests/hybrid_android_views/pubspec.yaml
index efdb489..d8ca6b4 100644
--- a/dev/integration_tests/hybrid_android_views/pubspec.yaml
+++ b/dev/integration_tests/hybrid_android_views/pubspec.yaml
@@ -1,6 +1,8 @@
name: hybrid_platform_views
description: An integration test for hybrid composition on Android
version: 1.0.0+1
+environment:
+ sdk: '>=2.9.0 <3.0.0'
dependencies:
flutter:
diff --git a/dev/integration_tests/image_loading/pubspec.yaml b/dev/integration_tests/image_loading/pubspec.yaml
index 428805a..dd3111b 100644
--- a/dev/integration_tests/image_loading/pubspec.yaml
+++ b/dev/integration_tests/image_loading/pubspec.yaml
@@ -1,5 +1,7 @@
name: image_loading
description: Integration testing library for Android semantics
+environment:
+ sdk: '>=2.9.0 <3.0.0'
dependencies:
flutter:
diff --git a/dev/integration_tests/ios_add2app/flutterapp/.gitignore b/dev/integration_tests/ios_add2app/flutterapp/.gitignore
new file mode 100644
index 0000000..cdecf14
--- /dev/null
+++ b/dev/integration_tests/ios_add2app/flutterapp/.gitignore
@@ -0,0 +1,41 @@
+.DS_Store
+.dart_tool/
+
+.packages
+.pub/
+
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+*.swp
+profile
+
+DerivedData/
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+build/
+.android/
+.ios/
+.flutter-plugins
diff --git a/dev/integration_tests/ios_add2app/flutterapp/pubspec.yaml b/dev/integration_tests/ios_add2app/flutterapp/pubspec.yaml
index 42c8c49..92e9ef0 100644
--- a/dev/integration_tests/ios_add2app/flutterapp/pubspec.yaml
+++ b/dev/integration_tests/ios_add2app/flutterapp/pubspec.yaml
@@ -22,7 +22,7 @@
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: 0.1.3
+ cupertino_icons: 1.0.0
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -99,4 +99,4 @@
androidPackage: com.example.iosadd2appflutter
iosBundleIdentifier: com.example.iosAdd2appFlutter
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml b/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml
index a98e3ca..920036b 100644
--- a/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml
+++ b/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml
@@ -22,7 +22,7 @@
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: 0.1.3
+ cupertino_icons: 1.0.0
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -99,4 +99,4 @@
androidPackage: com.example.iosadd2appflutter
iosBundleIdentifier: com.example.iosAdd2appFlutter
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/dev/integration_tests/ios_app_with_extensions/pubspec.yaml b/dev/integration_tests/ios_app_with_extensions/pubspec.yaml
index 7c17802..5bd1be4 100644
--- a/dev/integration_tests/ios_app_with_extensions/pubspec.yaml
+++ b/dev/integration_tests/ios_app_with_extensions/pubspec.yaml
@@ -20,7 +20,7 @@
sdk: flutter
# This integration test includes a watchOS pod. Add a Flutter plugin
# to prompt the tool to run pod install.
- device_info: 0.4.2+8
+ device_info: 0.4.2+9
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -91,4 +91,4 @@
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
-# PUBSPEC CHECKSUM: c70b
+# PUBSPEC CHECKSUM: 220c
diff --git a/dev/integration_tests/non_nullable/pubspec.yaml b/dev/integration_tests/non_nullable/pubspec.yaml
index be4aa81..c51ec7c 100644
--- a/dev/integration_tests/non_nullable/pubspec.yaml
+++ b/dev/integration_tests/non_nullable/pubspec.yaml
@@ -10,7 +10,7 @@
dependencies:
flutter:
sdk: flutter
- cupertino_icons: 0.1.3
+ cupertino_icons: 1.0.0
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -39,4 +39,4 @@
flutter:
uses-material-design: true
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/dev/integration_tests/web_compile_tests/pubspec.yaml b/dev/integration_tests/web_compile_tests/pubspec.yaml
index f546711..b429c78 100644
--- a/dev/integration_tests/web_compile_tests/pubspec.yaml
+++ b/dev/integration_tests/web_compile_tests/pubspec.yaml
@@ -1,4 +1,6 @@
name: web_compile_tests
+environment:
+ sdk: '>=2.9.0 <3.0.0'
dependencies:
flutter:
diff --git a/dev/integration_tests/web_e2e_tests/pubspec.yaml b/dev/integration_tests/web_e2e_tests/pubspec.yaml
index c9d4cab..e14d95d 100644
--- a/dev/integration_tests/web_e2e_tests/pubspec.yaml
+++ b/dev/integration_tests/web_e2e_tests/pubspec.yaml
@@ -19,7 +19,7 @@
sdk: flutter
flutter_test:
sdk: flutter
- integration_test: 0.9.2
+ integration_test: 0.9.2+1
http: 0.12.2
test: 1.16.0-nullsafety.5
@@ -78,4 +78,4 @@
webkit_inspection_protocol: 0.7.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
yaml: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-# PUBSPEC CHECKSUM: eb1f
+# PUBSPEC CHECKSUM: f37b
diff --git a/dev/manual_tests/lib/actions.dart b/dev/manual_tests/lib/actions.dart
index f830178..b929c35 100644
--- a/dev/manual_tests/lib/actions.dart
+++ b/dev/manual_tests/lib/actions.dart
@@ -194,13 +194,13 @@
class UndoAction extends Action<UndoIntent> {
@override
bool isEnabled(UndoIntent intent) {
- final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext, nullOk: true) as UndoableActionDispatcher;
+ final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext) as UndoableActionDispatcher;
return manager.canUndo;
}
@override
void invoke(UndoIntent intent) {
- final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext, nullOk: true) as UndoableActionDispatcher;
+ final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext) as UndoableActionDispatcher;
manager?.undo();
}
}
@@ -212,13 +212,13 @@
class RedoAction extends Action<RedoIntent> {
@override
bool isEnabled(RedoIntent intent) {
- final UndoableActionDispatcher manager = Actions.of(primaryFocus.context, nullOk: true) as UndoableActionDispatcher;
+ final UndoableActionDispatcher manager = Actions.of(primaryFocus.context) as UndoableActionDispatcher;
return manager.canRedo;
}
@override
RedoAction invoke(RedoIntent intent) {
- final UndoableActionDispatcher manager = Actions.of(primaryFocus.context, nullOk: true) as UndoableActionDispatcher;
+ final UndoableActionDispatcher manager = Actions.of(primaryFocus.context) as UndoableActionDispatcher;
manager?.redo();
return this;
}
diff --git a/dev/manual_tests/lib/focus.dart b/dev/manual_tests/lib/focus.dart
index 7f6a782..e06e0da 100644
--- a/dev/manual_tests/lib/focus.dart
+++ b/dev/manual_tests/lib/focus.dart
@@ -97,7 +97,7 @@
super.dispose();
}
- bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
+ KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
print('Scope got key event: ${event.logicalKey}, $node');
print('Keys down: ${RawKeyboard.instance.keysPressed}');
@@ -106,31 +106,31 @@
if (event.isShiftPressed) {
print('Moving to previous.');
node.previousFocus();
- return true;
+ return KeyEventResult.handled;
} else {
print('Moving to next.');
node.nextFocus();
- return true;
+ return KeyEventResult.handled;
}
}
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
node.focusInDirection(TraversalDirection.left);
- return true;
+ return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
node.focusInDirection(TraversalDirection.right);
- return true;
+ return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
node.focusInDirection(TraversalDirection.up);
- return true;
+ return KeyEventResult.handled;
}
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
node.focusInDirection(TraversalDirection.down);
- return true;
+ return KeyEventResult.handled;
}
}
- return false;
+ return KeyEventResult.ignored;
}
@override
diff --git a/dev/manual_tests/lib/raw_keyboard.dart b/dev/manual_tests/lib/raw_keyboard.dart
index e08b260..a511ef0 100644
--- a/dev/manual_tests/lib/raw_keyboard.dart
+++ b/dev/manual_tests/lib/raw_keyboard.dart
@@ -37,11 +37,11 @@
super.dispose();
}
- bool _handleKeyEvent(FocusNode node, RawKeyEvent event) {
+ KeyEventResult _handleKeyEvent(FocusNode node, RawKeyEvent event) {
setState(() {
_event = event;
});
- return false;
+ return KeyEventResult.ignored;
}
String _asHex(int value) => value != null ? '0x${value.toRadixString(16)}' : 'null';
diff --git a/dev/tools/dartdoc.dart b/dev/tools/dartdoc.dart
index f7768fc..a726bb4 100644
--- a/dev/tools/dartdoc.dart
+++ b/dev/tools/dartdoc.dart
@@ -139,7 +139,6 @@
final List<String> dartdocArgs = <String>[
...dartdocBaseArgs,
'--allow-tools',
- '--enable-experiment=non-nullable',
if (args['json'] as bool) '--json',
if (args['validate-links'] as bool) '--validate-links' else '--no-validate-links',
'--link-to-source-excludes', '../../bin/cache',
diff --git a/examples/image_list/pubspec.yaml b/examples/image_list/pubspec.yaml
index f2488f8..bfa737b 100644
--- a/examples/image_list/pubspec.yaml
+++ b/examples/image_list/pubspec.yaml
@@ -12,7 +12,7 @@
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: 0.1.3
+ cupertino_icons: 1.0.0
characters: 1.1.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.15.0-nullsafety.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -54,4 +54,4 @@
assets:
- images/coast.jpg
-# PUBSPEC CHECKSUM: 9423
+# PUBSPEC CHECKSUM: 2920
diff --git a/examples/layers/analysis_options.yaml b/examples/layers/analysis_options.yaml
new file mode 100644
index 0000000..03733cd
--- /dev/null
+++ b/examples/layers/analysis_options.yaml
@@ -0,0 +1,13 @@
+# Use the parent analysis options settings and enable null-experiment.
+
+include: ../../analysis_options.yaml
+
+analyzer:
+ enable-experiment:
+ - non-nullable
+ errors:
+ always_require_non_null_named_parameters: false # not needed with nnbd
+ type_init_formals: false # https://github.com/dart-lang/linter/issues/2192
+ unrelated_type_equality_checks: false # https://github.com/dart-lang/linter/issues/2196
+ void_checks: false # https://github.com/dart-lang/linter/issues/2185
+ unnecessary_null_comparison: false # Turned off until null-safe rollout is complete.
diff --git a/examples/layers/rendering/custom_coordinate_systems.dart b/examples/layers/rendering/custom_coordinate_systems.dart
index 5307b56..0402b77 100644
--- a/examples/layers/rendering/custom_coordinate_systems.dart
+++ b/examples/layers/rendering/custom_coordinate_systems.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.8
+// @dart = 2.10
// This example shows how to build a render tree with a non-cartesian coordinate
// system. Most of the guts of this examples are in src/sector_layout.dart.
diff --git a/examples/layers/rendering/src/sector_layout.dart b/examples/layers/rendering/src/sector_layout.dart
index 6cb2d3c..dbef998 100644
--- a/examples/layers/rendering/src/sector_layout.dart
+++ b/examples/layers/rendering/src/sector_layout.dart
@@ -2,13 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.8
+// @dart = 2.10
import 'dart:math' as math;
import 'package:flutter/rendering.dart';
import 'package:flutter/gestures.dart';
-import 'package:meta/meta.dart';
const double kTwoPi = 2 * math.pi;
@@ -33,11 +32,11 @@
final double maxDeltaTheta;
double constrainDeltaRadius(double deltaRadius) {
- return deltaRadius.clamp(minDeltaRadius, maxDeltaRadius) as double;
+ return deltaRadius.clamp(minDeltaRadius, maxDeltaRadius);
}
double constrainDeltaTheta(double deltaTheta) {
- return deltaTheta.clamp(minDeltaTheta, maxDeltaTheta) as double;
+ return deltaTheta.clamp(minDeltaTheta, maxDeltaTheta);
}
@override
@@ -49,7 +48,7 @@
@override
bool debugAssertIsValid({
bool isAppliedConstraint = false,
- InformationCollector informationCollector,
+ InformationCollector? informationCollector,
}) {
assert(isNormalized);
return isNormalized;
@@ -102,7 +101,7 @@
// RenderSectors always use SectorParentData subclasses, as they need to be
// able to read their position information for painting and hit testing.
@override
- SectorParentData get parentData => super.parentData as SectorParentData;
+ SectorParentData? get parentData => super.parentData as SectorParentData?;
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
return SectorDimensions.withConstraints(constraints);
@@ -145,27 +144,27 @@
@override
Rect get semanticBounds => Rect.fromLTWH(-deltaRadius, -deltaRadius, 2.0 * deltaRadius, 2.0 * deltaRadius);
- bool hitTest(SectorHitTestResult result, { double radius, double theta }) {
- if (radius < parentData.radius || radius >= parentData.radius + deltaRadius ||
- theta < parentData.theta || theta >= parentData.theta + deltaTheta)
+ bool hitTest(SectorHitTestResult result, { required double radius, required double theta }) {
+ if (radius < parentData!.radius || radius >= parentData!.radius + deltaRadius ||
+ theta < parentData!.theta || theta >= parentData!.theta + deltaTheta)
return false;
hitTestChildren(result, radius: radius, theta: theta);
result.add(SectorHitTestEntry(this, radius: radius, theta: theta));
return true;
}
- void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) { }
+ void hitTestChildren(SectorHitTestResult result, { required double radius, required double theta }) { }
- double deltaRadius;
- double deltaTheta;
+ late double deltaRadius;
+ late double deltaTheta;
}
abstract class RenderDecoratedSector extends RenderSector {
- RenderDecoratedSector(BoxDecoration decoration) : _decoration = decoration;
+ RenderDecoratedSector(BoxDecoration? decoration) : _decoration = decoration;
- BoxDecoration _decoration;
- BoxDecoration get decoration => _decoration;
- set decoration(BoxDecoration value) {
+ BoxDecoration? _decoration;
+ BoxDecoration? get decoration => _decoration;
+ set decoration(BoxDecoration? value) {
if (value == _decoration)
return;
_decoration = value;
@@ -182,16 +181,16 @@
if (_decoration == null)
return;
- if (_decoration.color != null) {
+ if (_decoration!.color != null) {
final Canvas canvas = context.canvas;
- final Paint paint = Paint()..color = _decoration.color;
+ final Paint paint = Paint()..color = _decoration!.color!;
final Path path = Path();
- final double outerRadius = parentData.radius + deltaRadius;
+ final double outerRadius = parentData!.radius + deltaRadius;
final Rect outerBounds = Rect.fromLTRB(offset.dx-outerRadius, offset.dy-outerRadius, offset.dx+outerRadius, offset.dy+outerRadius);
- path.arcTo(outerBounds, parentData.theta, deltaTheta, true);
- final double innerRadius = parentData.radius;
+ path.arcTo(outerBounds, parentData!.theta, deltaTheta, true);
+ final double innerRadius = parentData!.radius;
final Rect innerBounds = Rect.fromLTRB(offset.dx-innerRadius, offset.dy-innerRadius, offset.dx+innerRadius, offset.dy+innerRadius);
- path.arcTo(innerBounds, parentData.theta + deltaTheta, -deltaTheta, false);
+ path.arcTo(innerBounds, parentData!.theta + deltaTheta, -deltaTheta, false);
path.close();
canvas.drawPath(path, paint);
}
@@ -202,25 +201,25 @@
class SectorChildListParentData extends SectorParentData with ContainerParentDataMixin<RenderSector> { }
class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRenderObjectMixin<RenderSector, SectorChildListParentData> {
- RenderSectorWithChildren(BoxDecoration decoration) : super(decoration);
+ RenderSectorWithChildren(BoxDecoration? decoration) : super(decoration);
@override
- void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) {
- RenderSector child = lastChild;
+ void hitTestChildren(SectorHitTestResult result, { required double radius, required double theta }) {
+ RenderSector? child = lastChild;
while (child != null) {
if (child.hitTest(result, radius: radius, theta: theta))
return;
- final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+ final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
child = childParentData.previousSibling;
}
}
@override
void visitChildren(RenderObjectVisitor visitor) {
- RenderSector child = lastChild;
+ RenderSector? child = lastChild;
while (child != null) {
visitor(child);
- final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+ final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
child = childParentData.previousSibling;
}
}
@@ -230,7 +229,7 @@
// lays out RenderSector children in a ring
RenderSectorRing({
- BoxDecoration decoration,
+ BoxDecoration? decoration,
double deltaRadius = double.infinity,
double padding = 0.0,
}) : _padding = padding,
@@ -275,7 +274,7 @@
final double paddingTheta = math.atan(padding / (radius + outerDeltaRadius));
double innerTheta = paddingTheta; // increments with each child
double remainingDeltaTheta = math.max(0.0, constraints.maxDeltaTheta - (innerTheta + paddingTheta));
- RenderSector child = firstChild;
+ RenderSector? child = firstChild;
while (child != null) {
final SectorConstraints innerConstraints = SectorConstraints(
maxDeltaRadius: innerDeltaRadius,
@@ -284,7 +283,7 @@
final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
innerTheta += childDimensions.deltaTheta;
remainingDeltaTheta -= childDimensions.deltaTheta;
- final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+ final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
child = childParentData.nextSibling;
if (child != null) {
innerTheta += paddingTheta;
@@ -302,23 +301,23 @@
deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
assert(deltaRadius < double.infinity);
final double innerDeltaRadius = deltaRadius - padding * 2.0;
- final double childRadius = parentData.radius + padding;
- final double paddingTheta = math.atan(padding / (parentData.radius + deltaRadius));
+ final double childRadius = parentData!.radius + padding;
+ final double paddingTheta = math.atan(padding / (parentData!.radius + deltaRadius));
double innerTheta = paddingTheta; // increments with each child
double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta);
- RenderSector child = firstChild;
+ RenderSector? child = firstChild;
while (child != null) {
final SectorConstraints innerConstraints = SectorConstraints(
maxDeltaRadius: innerDeltaRadius,
maxDeltaTheta: remainingDeltaTheta,
);
assert(child.parentData is SectorParentData);
- child.parentData.theta = innerTheta;
- child.parentData.radius = childRadius;
+ child.parentData!.theta = innerTheta;
+ child.parentData!.radius = childRadius;
child.layout(innerConstraints, parentUsesSize: true);
innerTheta += child.deltaTheta;
remainingDeltaTheta -= child.deltaTheta;
- final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+ final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
child = childParentData.nextSibling;
if (child != null) {
innerTheta += paddingTheta;
@@ -334,10 +333,10 @@
void paint(PaintingContext context, Offset offset) {
// TODO(ianh): avoid code duplication
super.paint(context, offset);
- RenderSector child = firstChild;
+ RenderSector? child = firstChild;
while (child != null) {
context.paintChild(child, offset);
- final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+ final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
child = childParentData.nextSibling;
}
}
@@ -348,7 +347,7 @@
// lays out RenderSector children in a stack
RenderSectorSlice({
- BoxDecoration decoration,
+ BoxDecoration? decoration,
double deltaTheta = kTwoPi,
double padding = 0.0,
}) : _padding = padding, _desiredDeltaTheta = deltaTheta, super(decoration);
@@ -384,12 +383,12 @@
@override
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
assert(parentData is SectorParentData);
- final double paddingTheta = math.atan(padding / parentData.radius);
+ final double paddingTheta = math.atan(padding / parentData!.radius);
final double outerDeltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
final double innerDeltaTheta = outerDeltaTheta - paddingTheta * 2.0;
- double childRadius = parentData.radius + padding;
+ double childRadius = parentData!.radius + padding;
double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
- RenderSector child = firstChild;
+ RenderSector? child = firstChild;
while (child != null) {
final SectorConstraints innerConstraints = SectorConstraints(
maxDeltaRadius: remainingDeltaRadius,
@@ -398,13 +397,13 @@
final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
childRadius += childDimensions.deltaRadius;
remainingDeltaRadius -= childDimensions.deltaRadius;
- final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+ final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
child = childParentData.nextSibling;
childRadius += padding;
remainingDeltaRadius -= padding;
}
return SectorDimensions.withConstraints(constraints,
- deltaRadius: childRadius - parentData.radius,
+ deltaRadius: childRadius - parentData!.radius,
deltaTheta: outerDeltaTheta);
}
@@ -413,28 +412,28 @@
assert(parentData is SectorParentData);
deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
assert(deltaTheta <= kTwoPi);
- final double paddingTheta = math.atan(padding / parentData.radius);
- final double innerTheta = parentData.theta + paddingTheta;
+ final double paddingTheta = math.atan(padding / parentData!.radius);
+ final double innerTheta = parentData!.theta + paddingTheta;
final double innerDeltaTheta = deltaTheta - paddingTheta * 2.0;
- double childRadius = parentData.radius + padding;
+ double childRadius = parentData!.radius + padding;
double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
- RenderSector child = firstChild;
+ RenderSector? child = firstChild;
while (child != null) {
final SectorConstraints innerConstraints = SectorConstraints(
maxDeltaRadius: remainingDeltaRadius,
maxDeltaTheta: innerDeltaTheta,
);
- child.parentData.theta = innerTheta;
- child.parentData.radius = childRadius;
+ child.parentData!.theta = innerTheta;
+ child.parentData!.radius = childRadius;
child.layout(innerConstraints, parentUsesSize: true);
childRadius += child.deltaRadius;
remainingDeltaRadius -= child.deltaRadius;
- final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+ final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
child = childParentData.nextSibling;
childRadius += padding;
remainingDeltaRadius -= padding;
}
- deltaRadius = childRadius - parentData.radius;
+ deltaRadius = childRadius - parentData!.radius;
}
// offset must point to the center of our circle
@@ -443,11 +442,11 @@
void paint(PaintingContext context, Offset offset) {
// TODO(ianh): avoid code duplication
super.paint(context, offset);
- RenderSector child = firstChild;
+ RenderSector? child = firstChild;
while (child != null) {
assert(child.parentData is SectorChildListParentData);
context.paintChild(child, offset);
- final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
+ final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
child = childParentData.nextSibling;
}
}
@@ -456,7 +455,7 @@
class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChildMixin<RenderSector> {
- RenderBoxToRenderSectorAdapter({ double innerRadius = 0.0, RenderSector child })
+ RenderBoxToRenderSectorAdapter({ double innerRadius = 0.0, RenderSector? child })
: _innerRadius = innerRadius {
this.child = child;
}
@@ -507,13 +506,13 @@
double height = double.infinity,
}) {
assert(child is RenderSector);
- assert(child.parentData is SectorParentData);
+ assert(child!.parentData is SectorParentData);
assert(width != null);
assert(height != null);
if (!width.isFinite && !height.isFinite)
return Size.zero;
final double maxChildDeltaRadius = math.max(0.0, math.min(width, height) / 2.0 - innerRadius);
- final SectorDimensions childDimensions = child.getIntrinsicDimensions(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius);
+ final SectorDimensions childDimensions = child!.getIntrinsicDimensions(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius);
final double dimension = (innerRadius + childDimensions.deltaRadius) * 2.0;
return Size.square(dimension);
}
@@ -526,12 +525,12 @@
return;
}
assert(child is RenderSector);
- assert(child.parentData is SectorParentData);
+ assert(child!.parentData is SectorParentData);
final double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius;
- child.parentData.radius = innerRadius;
- child.parentData.theta = 0.0;
- child.layout(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true);
- final double dimension = (innerRadius + child.deltaRadius) * 2.0;
+ child!.parentData!.radius = innerRadius;
+ child!.parentData!.theta = 0.0;
+ child!.layout(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true);
+ final double dimension = (innerRadius + child!.deltaRadius) * 2.0;
size = constraints.constrain(Size(dimension, dimension));
}
@@ -541,12 +540,12 @@
if (child != null) {
final Rect bounds = offset & size;
// we move the offset to the center of the circle for the RenderSectors
- context.paintChild(child, bounds.center);
+ context.paintChild(child!, bounds.center);
}
}
@override
- bool hitTest(BoxHitTestResult result, { Offset position }) {
+ bool hitTest(BoxHitTestResult result, { required Offset position }) {
if (child == null)
return false;
double x = position.dx;
@@ -559,11 +558,11 @@
final double theta = (math.atan2(x, -y) - math.pi / 2.0) % kTwoPi;
if (radius < innerRadius)
return false;
- if (radius >= innerRadius + child.deltaRadius)
+ if (radius >= innerRadius + child!.deltaRadius)
return false;
- if (theta > child.deltaTheta)
+ if (theta > child!.deltaTheta)
return false;
- child.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta);
+ child!.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta);
result.add(BoxHitTestEntry(this, position));
return true;
}
@@ -634,7 +633,7 @@
/// Creates a box hit test entry.
///
/// The [radius] and [theta] argument must not be null.
- SectorHitTestEntry(RenderSector target, { @required this.radius, @required this.theta })
+ SectorHitTestEntry(RenderSector target, { required this.radius, required this.theta })
: assert(radius != null),
assert(theta != null),
super(target);
diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart
index 03016b8..978fb4a 100644
--- a/packages/flutter/lib/src/animation/animation_controller.dart
+++ b/packages/flutter/lib/src/animation/animation_controller.dart
@@ -645,18 +645,27 @@
_checkStatusChanged();
}
- /// Drives the animation with a critically damped spring (within [lowerBound]
- /// and [upperBound]) and initial velocity.
+ /// Drives the animation with a spring (within [lowerBound] and [upperBound])
+ /// and initial velocity.
///
/// If velocity is positive, the animation will complete, otherwise it will
/// dismiss.
///
+ /// The [springDescription] parameter can be used to specify a custom [SpringType.criticallyDamped]
+ /// or [SpringType.overDamped] spring to drive the animation with. Defaults to null, which uses a
+ /// [SpringType.criticallyDamped] spring. See [SpringDescription.withDampingRatio] for how
+ /// to create a suitable [SpringDescription].
+ ///
+ /// The resulting spring simulation cannot be of type [SpringType.underDamped],
+ /// as this can lead to unexpected look of the produced animation.
+ ///
/// Returns a [TickerFuture] that completes when the animation is complete.
///
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
- TickerFuture fling({ double velocity = 1.0, AnimationBehavior? animationBehavior }) {
+ TickerFuture fling({ double velocity = 1.0, SpringDescription? springDescription, AnimationBehavior? animationBehavior }) {
+ springDescription ??= _kFlingSpringDescription;
_direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
: upperBound + _kFlingTolerance.distance;
@@ -673,8 +682,13 @@
break;
}
}
- final Simulation simulation = SpringSimulation(_kFlingSpringDescription, value, target, velocity * scale)
+ final SpringSimulation simulation = SpringSimulation(springDescription, value, target, velocity * scale)
..tolerance = _kFlingTolerance;
+ assert(
+ simulation.type != SpringType.underDamped,
+ 'The resulting spring simulation is of type SpringType.underDamped.\n'
+ 'This can lead to unexpected look of the animation, please adjust the springDescription parameter'
+ );
stop();
return _startSimulation(simulation);
}
diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart
index a74d104..eaeb4e5 100644
--- a/packages/flutter/lib/src/cupertino/context_menu.dart
+++ b/packages/flutter/lib/src/cupertino/context_menu.dart
@@ -731,7 +731,7 @@
}
@override
- bool didPop(T result) {
+ bool didPop(T? result) {
_updateTweenRects();
return super.didPop(result);
}
diff --git a/packages/flutter/lib/src/cupertino/icons.dart b/packages/flutter/lib/src/cupertino/icons.dart
index e27f3e7..671e0af 100644
--- a/packages/flutter/lib/src/cupertino/icons.dart
+++ b/packages/flutter/lib/src/cupertino/icons.dart
@@ -21,7 +21,7 @@
/// cupertino_icons: ^1.0.0
/// ```
///
-/// For a map of available icons for use, see <http://flutter.github.io/cupertino_icons>
+/// For a map of available icons for use, see <https://flutter.github.io/cupertino_icons>
/// for cupertino_icons version >1.0.0.
///
/// See <https://github.com/flutter/cupertino_icons/blob/master/map.png> for
diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart
index f108e49..0ae2590 100644
--- a/packages/flutter/lib/src/cupertino/nav_bar.dart
+++ b/packages/flutter/lib/src/cupertino/nav_bar.dart
@@ -1379,15 +1379,18 @@
// Replicate the Icon logic here to get a tightly sized icon and add
// custom non-square padding.
- Widget iconWidget = Text.rich(
- TextSpan(
- text: String.fromCharCode(CupertinoIcons.back.codePoint),
- style: TextStyle(
- inherit: false,
- color: textStyle.color,
- fontSize: 34.0,
- fontFamily: CupertinoIcons.back.fontFamily,
- package: CupertinoIcons.back.fontPackage,
+ Widget iconWidget = Padding(
+ padding: const EdgeInsetsDirectional.only(start: 6, end: 2),
+ child: Text.rich(
+ TextSpan(
+ text: String.fromCharCode(CupertinoIcons.back.codePoint),
+ style: TextStyle(
+ inherit: false,
+ color: textStyle.color,
+ fontSize: 30.0,
+ fontFamily: CupertinoIcons.back.fontFamily,
+ package: CupertinoIcons.back.fontPackage,
+ ),
),
),
);
diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart
index ace6af9..d1ddf5d 100644
--- a/packages/flutter/lib/src/cupertino/route.dart
+++ b/packages/flutter/lib/src/cupertino/route.dart
@@ -1049,7 +1049,7 @@
/// * [CupertinoActionSheet], which is the widget usually returned by the
/// `builder` argument to [showCupertinoModalPopup].
/// * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
-Future<T> showCupertinoModalPopup<T>({
+Future<T?> showCupertinoModalPopup<T>({
required BuildContext context,
required WidgetBuilder builder,
ImageFilter? filter,
@@ -1134,7 +1134,7 @@
/// * [showDialog], which displays a Material-style dialog.
/// * [showGeneralDialog], which allows for customization of the dialog popup.
/// * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
-Future<T> showCupertinoDialog<T>({
+Future<T?> showCupertinoDialog<T>({
required BuildContext context,
required WidgetBuilder builder,
bool useRootNavigator = true,
diff --git a/packages/flutter/lib/src/cupertino/slider.dart b/packages/flutter/lib/src/cupertino/slider.dart
index 6eab333..697f100 100644
--- a/packages/flutter/lib/src/cupertino/slider.dart
+++ b/packages/flutter/lib/src/cupertino/slider.dart
@@ -548,6 +548,7 @@
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = isInteractive;
+ config.isSlider = true;
if (isInteractive) {
config.textDirection = textDirection;
config.onIncrease = _increaseAction;
diff --git a/packages/flutter/lib/src/cupertino/tab_scaffold.dart b/packages/flutter/lib/src/cupertino/tab_scaffold.dart
index 2c65a09..306a6ec 100644
--- a/packages/flutter/lib/src/cupertino/tab_scaffold.dart
+++ b/packages/flutter/lib/src/cupertino/tab_scaffold.dart
@@ -619,8 +619,9 @@
}
@override
- CupertinoTabController fromPrimitives(Object data) {
- return CupertinoTabController(initialIndex: data as int);
+ CupertinoTabController fromPrimitives(Object? data) {
+ assert(data != null);
+ return CupertinoTabController(initialIndex: data! as int);
}
@override
diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart
index 0dc8027..5d3e0dd 100644
--- a/packages/flutter/lib/src/cupertino/text_field.dart
+++ b/packages/flutter/lib/src/cupertino/text_field.dart
@@ -46,11 +46,11 @@
darkColor: Color(0xFF050505),
);
-// Value inspected from Xcode 11 & iOS 13.0 Simulator.
+// Value inspected from Xcode 12 & iOS 14.0 Simulator.
// Note it may not be consistent with https://developer.apple.com/design/resources/.
const CupertinoDynamicColor _kClearButtonColor = CupertinoDynamicColor.withBrightness(
- color: Color(0xFF636366),
- darkColor: Color(0xFFAEAEB2),
+ color: Color(0x33000000),
+ darkColor: Color(0x33FFFFFF),
);
// An eyeballed value that moves the cursor slightly left of where it is
diff --git a/packages/flutter/lib/src/cupertino/text_selection.dart b/packages/flutter/lib/src/cupertino/text_selection.dart
index 90e51e6..b2c86d2 100644
--- a/packages/flutter/lib/src/cupertino/text_selection.dart
+++ b/packages/flutter/lib/src/cupertino/text_selection.dart
@@ -781,7 +781,7 @@
}
if (slot is IndexedSlot) {
assert(renderObject.debugValidateChild(child));
- renderObject.insert(child as RenderBox, after: slot.value?.renderObject as RenderBox);
+ renderObject.insert(child as RenderBox, after: slot.value?.renderObject as RenderBox?);
return;
}
assert(false, 'slot must be _CupertinoTextSelectionToolbarItemsSlot or IndexedSlot');
@@ -1255,4 +1255,4 @@
class _NullWidget extends Widget {
@override
Element createElement() => throw UnimplementedError();
-}
\ No newline at end of file
+}
diff --git a/packages/flutter/lib/src/foundation/change_notifier.dart b/packages/flutter/lib/src/foundation/change_notifier.dart
index 0293cf4..f44cacb 100644
--- a/packages/flutter/lib/src/foundation/change_notifier.dart
+++ b/packages/flutter/lib/src/foundation/change_notifier.dart
@@ -147,7 +147,30 @@
/// Register a closure to be called when the object changes.
///
+ /// If the given closure is already registered, an additional instance is
+ /// added, and must be removed the same number of times it is added before it
+ /// will stop being called.
+ ///
/// This method must not be called after [dispose] has been called.
+ ///
+ /// {@template flutter.foundation.ChangeNotifier.addListener}
+ /// If a listener is added twice, and is removed once during an iteration
+ /// (e.g. in response to a notification), it will still be called again. If,
+ /// on the other hand, it is removed as many times as it was registered, then
+ /// it will no longer be called. This odd behavior is the result of the
+ /// [ChangeNotifier] not being able to determine which listener is being
+ /// removed, since they are identical, therefore it will conservatively still
+ /// call all the listeners when it knows that any are still registered.
+ ///
+ /// This surprising behavior can be unexpectedly observed when registering a
+ /// listener on two separate objects which are both forwarding all
+ /// registrations to a common upstream object.
+ /// {@endtemplate}
+ ///
+ /// See also:
+ ///
+ /// * [removeListener], which removes a previously registered closure from
+ /// the list of closures that are notified when the object changes.
@override
void addListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
@@ -161,18 +184,12 @@
///
/// This method must not be called after [dispose] has been called.
///
- /// If a listener had been added twice, and is removed once during an
- /// iteration (i.e. in response to a notification), it will still be called
- /// again. If, on the other hand, it is removed as many times as it was
- /// registered, then it will no longer be called. This odd behavior is the
- /// result of the [ChangeNotifier] not being able to determine which listener
- /// is being removed, since they are identical, and therefore conservatively
- /// still calling all the listeners when it knows that any are still
- /// registered.
+ /// {@macro flutter.foundation.ChangeNotifier.addListener}
///
- /// This surprising behavior can be unexpectedly observed when registering a
- /// listener on two separate objects which are both forwarding all
- /// registrations to a common upstream object.
+ /// See also:
+ ///
+ /// * [addListener], which registers a closure to be called when the object
+ /// changes.
@override
void removeListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
@@ -208,7 +225,7 @@
///
/// This method must not be called after [dispose] has been called.
///
- /// Surprising behavior can result when reentrantly removing a listener (i.e.
+ /// Surprising behavior can result when reentrantly removing a listener (e.g.
/// in response to a notification) that has been registered multiple times.
/// See the discussion at [removeListener].
@protected
diff --git a/packages/flutter/lib/src/foundation/collections.dart b/packages/flutter/lib/src/foundation/collections.dart
index d6f9f8f..d62b156 100644
--- a/packages/flutter/lib/src/foundation/collections.dart
+++ b/packages/flutter/lib/src/foundation/collections.dart
@@ -166,7 +166,7 @@
final int firstLength = middle - start;
final int secondLength = end - middle;
// secondLength is always the same as firstLength, or one greater.
- final List<T?> scratchSpace = List<T?>.filled(secondLength, null, growable: false);
+ final List<T> scratchSpace = List<T>.filled(secondLength, list[start]);
_mergeSort<T>(list, compare, middle, end, scratchSpace, 0);
final int firstTarget = end - firstLength;
_mergeSort<T>(list, compare, start, middle, list, firstTarget);
@@ -273,7 +273,7 @@
int Function(T, T) compare,
int start,
int end,
- List<T?> target,
+ List<T> target,
int targetOffset,
) {
final int length = end - start;
@@ -316,10 +316,10 @@
List<T> firstList,
int firstStart,
int firstEnd,
- List<T?> secondList,
+ List<T> secondList,
int secondStart,
int secondEnd,
- List<T?> target,
+ List<T> target,
int targetOffset,
) {
// No empty lists reaches here.
@@ -328,7 +328,7 @@
int cursor1 = firstStart;
int cursor2 = secondStart;
T firstElement = firstList[cursor1++];
- T secondElement = secondList[cursor2++] as T;
+ T secondElement = secondList[cursor2++];
while (true) {
if (compare(firstElement, secondElement) <= 0) {
target[targetOffset++] = firstElement;
@@ -340,7 +340,7 @@
} else {
target[targetOffset++] = secondElement;
if (cursor2 != secondEnd) {
- secondElement = secondList[cursor2++] as T;
+ secondElement = secondList[cursor2++];
continue;
}
// Second list empties first. Flushing first list here.
diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart
index 57dd8a2..0087473 100644
--- a/packages/flutter/lib/src/material/bottom_sheet.dart
+++ b/packages/flutter/lib/src/material/bottom_sheet.dart
@@ -644,7 +644,7 @@
/// * [DraggableScrollableSheet], which allows you to create a bottom sheet
/// that grows and then becomes scrollable once it reaches its maximum size.
/// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
-Future<T> showModalBottomSheet<T>({
+Future<T?> showModalBottomSheet<T>({
required BuildContext context,
required WidgetBuilder builder,
Color? backgroundColor,
diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart
index a944f75..7b93e5d 100644
--- a/packages/flutter/lib/src/material/dialog.dart
+++ b/packages/flutter/lib/src/material/dialog.dart
@@ -955,7 +955,7 @@
/// * [showCupertinoDialog], which displays an iOS-style dialog.
/// * [showGeneralDialog], which allows for customization of the dialog popup.
/// * <https://material.io/design/components/dialogs.html>
-Future<T> showDialog<T>({
+Future<T?> showDialog<T>({
required BuildContext context,
WidgetBuilder? builder,
bool barrierDismissible = true,
diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart
index 9bc4cc6..cd3875b 100644
--- a/packages/flutter/lib/src/material/input_decorator.dart
+++ b/packages/flutter/lib/src/material/input_decorator.dart
@@ -980,7 +980,7 @@
_boxSize(icon).width
+ contentPadding.left
+ _boxSize(prefixIcon).width
- + (decoration.border!.isOutline ? 0.0 : _boxSize(suffixIcon).width)
+ + _boxSize(suffixIcon).width
+ contentPadding.right));
boxToBaseline[label] = _layoutLineBox(
label,
diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart
index 9403b75..59f7eda 100644
--- a/packages/flutter/lib/src/material/material_state.dart
+++ b/packages/flutter/lib/src/material/material_state.dart
@@ -120,7 +120,7 @@
/// }
/// ```
/// {@end-tool}
-abstract class MaterialStateColor extends Color implements MaterialStateProperty<Color?> {
+abstract class MaterialStateColor extends Color implements MaterialStateProperty<Color> {
/// Creates a [MaterialStateColor].
const MaterialStateColor(int defaultValue) : super(defaultValue);
diff --git a/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart b/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart
index 45fe8b4..5c11427 100644
--- a/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart
+++ b/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart
@@ -99,7 +99,7 @@
/// * [CalendarDatePicker], which provides the calendar grid used by the date picker dialog.
/// * [InputDatePickerFormField], which provides a text input field for entering dates.
///
-Future<DateTime> showDatePicker({
+Future<DateTime?> showDatePicker({
required BuildContext context,
required DateTime initialDate,
required DateTime firstDate,
diff --git a/packages/flutter/lib/src/material/pickers/date_range_picker_dialog.dart b/packages/flutter/lib/src/material/pickers/date_range_picker_dialog.dart
index 6e5042e..9b5ce42 100644
--- a/packages/flutter/lib/src/material/pickers/date_range_picker_dialog.dart
+++ b/packages/flutter/lib/src/material/pickers/date_range_picker_dialog.dart
@@ -105,7 +105,7 @@
/// select a single date.
/// * [DateTimeRange], which is used to describe a date range.
///
-Future<DateTimeRange> showDateRangePicker({
+Future<DateTimeRange?> showDateRangePicker({
required BuildContext context,
DateTimeRange? initialDateRange,
required DateTime firstDate,
diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart
index 47263dd..7c4df12 100644
--- a/packages/flutter/lib/src/material/popup_menu.dart
+++ b/packages/flutter/lib/src/material/popup_menu.dart
@@ -829,7 +829,7 @@
/// calling this method automatically.
/// * [SemanticsConfiguration.namesRoute], for a description of edge triggered
/// semantics.
-Future<T> showMenu<T>({
+Future<T?> showMenu<T>({
required BuildContext context,
required RelativeRect position,
required List<PopupMenuEntry<T>> items,
@@ -1088,7 +1088,7 @@
final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);
// Only show the menu if there is something to show
if (items.isNotEmpty) {
- showMenu<T>(
+ showMenu<T?>(
context: context,
elevation: widget.elevation ?? popupMenuTheme.elevation,
items: items,
@@ -1098,7 +1098,7 @@
color: widget.color ?? popupMenuTheme.color,
captureInheritedThemes: widget.captureInheritedThemes,
)
- .then<void>((T newValue) {
+ .then<void>((T? newValue) {
if (!mounted)
return null;
if (newValue == null) {
diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart
index ef07c18..35495f6 100644
--- a/packages/flutter/lib/src/material/range_slider.dart
+++ b/packages/flutter/lib/src/material/range_slider.dart
@@ -1529,6 +1529,7 @@
final SemanticsConfiguration config = SemanticsConfiguration();
config.isEnabled = isEnabled;
config.textDirection = textDirection;
+ config.isSlider = true;
if (isEnabled) {
config.onIncrease = increaseAction;
config.onDecrease = decreaseAction;
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index 7294b8f..5092e5b 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -2146,7 +2146,10 @@
/// See also:
///
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
- // TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated.
+ @Deprecated(
+ 'Use ScaffoldMessenger.showSnackBar. '
+ 'This feature was deprecated after v1.23.0-14.0.pre.'
+ )
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackbar) {
_snackBarController ??= SnackBar.createAnimationController(vsync: this)
..addStatusListener(_handleSnackBarStatusChange);
@@ -2205,7 +2208,10 @@
/// See also:
///
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
- // TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated
+ @Deprecated(
+ 'Use ScaffoldMessenger.removeCurrentSnackBar. '
+ 'This feature was deprecated after v1.23.0-14.0.pre.'
+ )
void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
assert(reason != null);
@@ -2247,7 +2253,10 @@
/// See also:
///
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
- // TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated.
+ @Deprecated(
+ 'Use ScaffoldMessenger.hideCurrentSnackBar. '
+ 'This feature was deprecated after v1.23.0-14.0.pre.'
+ )
void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
assert(reason != null);
diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart
index 0e4aeb9..e4e8996 100644
--- a/packages/flutter/lib/src/material/search.dart
+++ b/packages/flutter/lib/src/material/search.dart
@@ -49,7 +49,7 @@
/// See also:
///
/// * [SearchDelegate] to define the content of the search page.
-Future<T> showSearch<T>({
+Future<T?> showSearch<T>({
required BuildContext context,
required SearchDelegate<T> delegate,
String? query = '',
@@ -387,7 +387,7 @@
}
@override
- void didComplete(T result) {
+ void didComplete(T? result) {
super.didComplete(result);
assert(delegate._route == this);
delegate._route = null;
diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart
index e58e2db..e77af47 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -707,6 +707,7 @@
return Semantics(
container: true,
+ slider: true,
child: FocusableActionDetector(
actions: _actionMap,
shortcuts: _shortcutMap,
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index d4e46c1..1b465e5 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -2175,7 +2175,7 @@
/// date picker.
/// * [TimePickerThemeData], which allows you to customize the colors,
/// typography, and shape of the time picker.
-Future<TimeOfDay> showTimePicker({
+Future<TimeOfDay?> showTimePicker({
required BuildContext context,
required TimeOfDay initialTime,
TransitionBuilder? builder,
diff --git a/packages/flutter/lib/src/painting/image_provider.dart b/packages/flutter/lib/src/painting/image_provider.dart
index 110ca73..8ccae23 100644
--- a/packages/flutter/lib/src/painting/image_provider.dart
+++ b/packages/flutter/lib/src/painting/image_provider.dart
@@ -379,12 +379,12 @@
/// [FlutterError.onError], and the method will return null.
///
/// A completed return value of null indicates that an error has occurred.
- Future<ImageCacheStatus> obtainCacheStatus({
+ Future<ImageCacheStatus?> obtainCacheStatus({
required ImageConfiguration configuration,
ImageErrorListener? handleError,
}) {
assert(configuration != null);
- final Completer<ImageCacheStatus> completer = Completer<ImageCacheStatus>();
+ final Completer<ImageCacheStatus?> completer = Completer<ImageCacheStatus?>();
_createErrorHandlerAndKey(
configuration,
(T key, ImageErrorListener innerHandleError) {
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart
index 067ba9b..5824927 100644
--- a/packages/flutter/lib/src/rendering/editable.dart
+++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -676,14 +676,24 @@
}
} else {
if (rightArrow && newSelection.extentOffset < _plainText.length) {
- final int nextExtent = nextCharacter(newSelection.extentOffset, _plainText);
+ int nextExtent;
+ if (!shift && !wordModifier && !lineModifier && newSelection.start != newSelection.end) {
+ nextExtent = newSelection.end;
+ } else {
+ nextExtent = nextCharacter(newSelection.extentOffset, _plainText);
+ }
final int distance = nextExtent - newSelection.extentOffset;
newSelection = newSelection.copyWith(extentOffset: nextExtent);
if (shift) {
_cursorResetLocation += distance;
}
} else if (leftArrow && newSelection.extentOffset > 0) {
- final int previousExtent = previousCharacter(newSelection.extentOffset, _plainText);
+ int previousExtent;
+ if (!shift && !wordModifier && !lineModifier && newSelection.start != newSelection.end) {
+ previousExtent = newSelection.start;
+ } else {
+ previousExtent = previousCharacter(newSelection.extentOffset, _plainText);
+ }
final int distance = newSelection.extentOffset - previousExtent;
newSelection = newSelection.copyWith(extentOffset: previousExtent);
if (shift) {
diff --git a/packages/flutter/lib/src/rendering/table.dart b/packages/flutter/lib/src/rendering/table.dart
index 476def3..35174c4 100644
--- a/packages/flutter/lib/src/rendering/table.dart
+++ b/packages/flutter/lib/src/rendering/table.dart
@@ -390,7 +390,7 @@
_textBaseline = textBaseline,
_defaultVerticalAlignment = defaultVerticalAlignment,
_configuration = configuration {
- _children = <RenderBox>[]..length = _columns * _rows;
+ _children = <RenderBox?>[]..length = _columns * _rows;
this.rowDecorations = rowDecorations; // must use setter to initialize box painters array
children?.forEach(addRow);
}
@@ -645,7 +645,7 @@
// update our internal values
_columns = columns;
_rows = cells.length ~/ columns;
- _children = cells.toList();
+ _children = List<RenderBox?>.from(cells);
assert(_children.length == rows * columns);
markNeedsLayout();
}
diff --git a/packages/flutter/lib/src/services/restoration.dart b/packages/flutter/lib/src/services/restoration.dart
index 42e33ee..54d9a94 100644
--- a/packages/flutter/lib/src/services/restoration.dart
+++ b/packages/flutter/lib/src/services/restoration.dart
@@ -151,13 +151,13 @@
return SynchronousFuture<RestorationBucket?>(_rootBucket);
}
if (_pendingRootBucket == null) {
- _pendingRootBucket = Completer<RestorationBucket>();
+ _pendingRootBucket = Completer<RestorationBucket?>();
_getRootBucketFromEngine();
}
return _pendingRootBucket!.future;
}
RestorationBucket? _rootBucket; // May be null to indicate that restoration is turned off.
- Completer<RestorationBucket>? _pendingRootBucket;
+ Completer<RestorationBucket?>? _pendingRootBucket;
bool _rootBucketIsValid = false;
/// Returns true for the frame after [rootBucket] has been replaced with a
@@ -186,7 +186,7 @@
void _parseAndHandleRestorationUpdateFromEngine(Map<dynamic, dynamic>? update) {
handleRestorationUpdateFromEngine(
enabled: update != null && update['enabled'] as bool,
- data: update == null ? null : update['data'] as Uint8List,
+ data: update == null ? null : update['data'] as Uint8List?,
);
}
@@ -580,10 +580,10 @@
/// * [remove], which removes a value from the bucket.
/// * [contains], which checks whether any value is stored under a given
/// restoration ID.
- P read<P>(String restorationId) {
+ P? read<P>(String restorationId) {
assert(_debugAssertNotDisposed());
assert(restorationId != null);
- return _rawValues[restorationId] as P;
+ return _rawValues[restorationId] as P?;
}
/// Stores the provided `value` of type `P` under the provided `restorationId`
@@ -624,11 +624,11 @@
/// * [write], which stores a value in the bucket.
/// * [contains], which checks whether any value is stored under a given
/// restoration ID.
- P remove<P>(String restorationId) {
+ P? remove<P>(String restorationId) {
assert(_debugAssertNotDisposed());
assert(restorationId != null);
final bool needsUpdate = _rawValues.containsKey(restorationId);
- final P result = _rawValues.remove(restorationId) as P;
+ final P? result = _rawValues.remove(restorationId) as P?;
if (_rawValues.isEmpty) {
_rawData.remove(_valuesMapKey);
}
diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart
index f66903a..f06f95a 100644
--- a/packages/flutter/lib/src/widgets/actions.dart
+++ b/packages/flutter/lib/src/widgets/actions.dart
@@ -6,6 +6,7 @@
import 'package:flutter/scheduler.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
import 'basic.dart';
import 'focus_manager.dart';
@@ -28,7 +29,7 @@
return parent;
}
-/// A class representing a particular configuration of an [Action].
+/// An abstract class representing a particular configuration of an [Action].
///
/// This class is what the [Shortcuts.shortcuts] map has as values, and is used
/// by an [ActionDispatcher] to look up an action and invoke it, giving it this
@@ -40,11 +41,12 @@
/// [Intent] using the [Actions] widget that most tightly encloses the given
/// [BuildContext].
@immutable
-class Intent with Diagnosticable {
+abstract class Intent with Diagnosticable {
/// A const constructor for an [Intent].
const Intent();
- /// An intent that can't be mapped to an action.
+ /// An intent that is mapped to a [DoNothingAction], which, as the name
+ /// implies, does nothing.
///
/// This Intent is mapped to an action in the [WidgetsApp] that does nothing,
/// so that it can be bound to a key in a [Shortcuts] widget in order to
@@ -92,6 +94,19 @@
/// [notifyActionListeners] to notify any listeners of the change.
bool isEnabled(covariant T intent) => true;
+ /// Indicates whether this action should treat key events mapped to this
+ /// action as being "handled" when it is invoked via the key event.
+ ///
+ /// If the key is handled, then no other key event handlers in the focus chain
+ /// will receive the event.
+ ///
+ /// If the key event is not handled, it will be passed back to the engine, and
+ /// continue to be processed there, allowing text fields and non-Flutter
+ /// widgets to receive the key event.
+ ///
+ /// The default implementation returns true.
+ bool consumesKey(covariant T intent) => true;
+
/// Called when the action is to be performed.
///
/// This is called by the [ActionDispatcher] when an action is invoked via
@@ -380,21 +395,25 @@
/// the action is a [ContextAction], then the context from the [primaryFocus]
/// is used.
///
- /// Returns the object returned from [Action.invoke] if the action was
- /// successfully invoked, and null if the action is not enabled. May also
- /// return null if [Action.invoke] returns null.
- Object? invokeAction(covariant Action<Intent> action, covariant Intent intent, [BuildContext? context]) {
+ /// Returns the object returned from [Action.invoke].
+ ///
+ /// The caller must receive a `true` result from [Action.isEnabled] before
+ /// calling this function. This function will assert if the action is not
+ /// enabled when called.
+ Object? invokeAction(
+ covariant Action<Intent> action,
+ covariant Intent intent, [
+ BuildContext? context,
+ ]) {
assert(action != null);
assert(intent != null);
- context ??= primaryFocus?.context;
- if (action.isEnabled(intent)) {
- if (action is ContextAction) {
- return action.invoke(intent, context);
- } else {
- return action.invoke(intent);
- }
+ assert(action.isEnabled(intent), 'Action must be enabled when calling invokeAction');
+ if (action is ContextAction) {
+ context ??= primaryFocus?.context;
+ return action.invoke(intent, context);
+ } else {
+ return action.invoke(intent);
}
- return null;
}
}
@@ -496,7 +515,11 @@
final Action<T>? action = Actions.find<T>(context, nullOk: nullOk);
if (action != null && action.isEnabled(intent)) {
return () {
- Actions.of(context).invokeAction(action, intent, context);
+ // Could be that the action was enabled when the closure was created,
+ // but is now no longer enabled, so check again.
+ if (action.isEnabled(intent)) {
+ Actions.of(context).invokeAction(action, intent, context);
+ }
};
}
return null;
@@ -507,12 +530,26 @@
/// Creates a dependency on the [Actions] widget that maps the bound action so
/// that if the actions change, the context will be rebuilt and find the
/// updated action.
- static Action<T>? find<T extends Intent>(BuildContext context, {bool nullOk = false}) {
+ ///
+ /// The optional `intent` argument supplies the type of the intent to look for
+ /// if the concrete type of the intent sought isn't available. If not
+ /// supplied, then `T` is used.
+ static Action<T>? find<T extends Intent>(BuildContext context, {bool nullOk = false, T? intent}) {
Action<T>? action;
+ // Specialize the type if a runtime example instance of the intent is given.
+ // This allows this function to be called by code that doesn't know the
+ // concrete type of the intent at compile time.
+ final Type type = intent?.runtimeType ?? T;
+ assert(type != Intent,
+ 'The type passed to "find" resolved to "Intent": either a non-Intent'
+ 'generic type argument or an example intent derived from Intent must be'
+ 'specified. Intent may be used as the generic type as long as the optional'
+ '"intent" argument is passed.');
+
_visitActionsAncestors(context, (InheritedElement element) {
final _ActionsMarker actions = element.widget as _ActionsMarker;
- final Action<T>? result = actions.actions[T] as Action<T>?;
+ final Action<T>? result = actions.actions[type] as Action<T>?;
if (result != null) {
context.dependOnInheritedElement(element);
action = result;
@@ -526,14 +563,14 @@
return true;
}
if (action == null) {
- throw FlutterError('Unable to find an action for a $T in an $Actions widget '
+ throw FlutterError('Unable to find an action for a $type in an $Actions widget '
'in the given context.\n'
"$Actions.find() was called on a context that doesn\'t contain an "
'$Actions widget with a mapping for the given intent type.\n'
'The context used was:\n'
' $context\n'
'The intent type requested was:\n'
- ' $T');
+ ' $type');
}
return true;
}());
@@ -543,31 +580,11 @@
/// Returns the [ActionDispatcher] associated with the [Actions] widget that
/// most tightly encloses the given [BuildContext].
///
- /// Will throw if no ambient [Actions] widget is found.
- ///
- /// If `nullOk` is set to true, then if no ambient [Actions] widget is found,
- /// this will return null.
- ///
- /// The `context` argument must not be null.
- static ActionDispatcher of(BuildContext context, {bool nullOk = false}) {
+ /// Will return a newly created [ActionDispatcher] if no ambient [Actions]
+ /// widget is found.
+ static ActionDispatcher of(BuildContext context) {
assert(context != null);
final _ActionsMarker? marker = context.dependOnInheritedWidgetOfExactType<_ActionsMarker>();
- assert(() {
- if (nullOk) {
- return true;
- }
- if (marker == null) {
- throw FlutterError('Unable to find an $Actions widget in the given context.\n'
- '$Actions.of() was called with a context that does not contain an '
- '$Actions widget.\n'
- 'No $Actions ancestor could be found starting from the context that '
- 'was passed to $Actions.of(). This can happen if the context comes '
- 'from a widget above those widgets.\n'
- 'The context used was:\n'
- ' $context');
- }
- return true;
- }());
return marker?.dispatcher ?? _findDispatcher(context);
}
@@ -632,9 +649,15 @@
}
return true;
}());
- // Invoke the action we found using the relevant dispatcher from the Actions
- // Element we found.
- return actionElement != null ? _findDispatcher(actionElement!).invokeAction(action!, intent, context) : null;
+ if (actionElement == null || action == null) {
+ return null;
+ }
+ if (action!.isEnabled(intent)) {
+ // Invoke the action we found using the relevant dispatcher from the Actions
+ // Element we found.
+ return _findDispatcher(actionElement!).invokeAction(action!, intent, context);
+ }
+ return null;
}
@override
@@ -1116,12 +1139,19 @@
}
}
-/// An [Intent], that, as the name implies, is bound to a [DoNothingAction].
+/// An [Intent], that is bound to a [DoNothingAction].
///
/// Attaching a [DoNothingIntent] to a [Shortcuts] mapping is one way to disable
-/// a keyboard shortcut defined by a widget higher in the widget hierarchy.
+/// a keyboard shortcut defined by a widget higher in the widget hierarchy and
+/// consume any key event that triggers it via a shortcut.
///
/// This intent cannot be subclassed.
+///
+/// See also:
+///
+/// * [DoNothingAndStopPropagationIntent], a similar intent that will not
+/// handle the key event, but will still keep it from being passed to other key
+/// handlers in the focus chain.
class DoNothingIntent extends Intent {
/// Creates a const [DoNothingIntent].
factory DoNothingIntent() => const DoNothingIntent._();
@@ -1130,17 +1160,61 @@
const DoNothingIntent._();
}
-/// An [Action], that, as the name implies, does nothing.
+/// An [Intent], that is bound to a [DoNothingAction], but, in addition to not
+/// performing an action, also stops the propagation of the key event bound to
+/// this intent to other key event handlers in the focus chain.
///
-/// Attaching a [DoNothingAction] to an [Actions] mapping is one way to disable
-/// an action defined by a widget higher in the widget hierarchy.
+/// Attaching a [DoNothingAndStopPropagationIntent] to a [Shortcuts.shortcuts]
+/// mapping is one way to disable a keyboard shortcut defined by a widget higher
+/// in the widget hierarchy. In addition, the bound [DoNothingAction] will
+/// return false from [DoNothingAction.consumesKey], causing the key bound to
+/// this intent to be passed on to the platform embedding as "not handled" with
+/// out passing it to other key handlers in the focus chain (e.g. parent
+/// `Shortcuts` widgets higher up in the chain).
///
-/// This action can be bound to any intent.
+/// This intent cannot be subclassed.
///
/// See also:
-/// - [DoNothingIntent], which is an intent that can be bound to a keystroke in
+///
+/// * [DoNothingIntent], a similar intent that will handle the key event.
+class DoNothingAndStopPropagationIntent extends Intent {
+ /// Creates a const [DoNothingAndStopPropagationIntent].
+ factory DoNothingAndStopPropagationIntent() => const DoNothingAndStopPropagationIntent._();
+
+ // Make DoNothingAndStopPropagationIntent constructor private so it can't be subclassed.
+ const DoNothingAndStopPropagationIntent._();
+}
+
+/// An [Action], that doesn't perform any action when invoked.
+///
+/// Attaching a [DoNothingAction] to an [Actions.actions] mapping is a way to
+/// disable an action defined by a widget higher in the widget hierarchy.
+///
+/// If [consumesKey] returns false, then not only will this action do nothing,
+/// but it will stop the propagation of the key event used to trigger it to
+/// other widgets in the focus chain and tell the embedding that the key wasn't
+/// handled, allowing text input fields or other non-Flutter elements to receive
+/// that key event. The return value of [consumesKey] can be set via the
+/// `consumesKey` argument to the constructor.
+///
+/// This action can be bound to any [Intent].
+///
+/// See also:
+/// - [DoNothingIntent], which is an intent that can be bound to a [KeySet] in
/// a [Shortcuts] widget to do nothing.
+/// - [DoNothingAndStopPropagationIntent], which is an intent that can be bound
+/// to a [KeySet] in a [Shortcuts] widget to do nothing and also stop key event
+/// propagation to other key handlers in the focus chain.
class DoNothingAction extends Action<Intent> {
+ /// Creates a [DoNothingAction].
+ ///
+ /// The optional [consumesKey] argument defaults to true.
+ DoNothingAction({bool consumesKey = true}) : _consumesKey = consumesKey;
+
+ @override
+ bool consumesKey(Intent intent) => _consumesKey;
+ final bool _consumesKey;
+
@override
void invoke(Intent intent) {}
}
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 333cf35..926daa7 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -1095,6 +1095,7 @@
/// The default value of [WidgetsApp.actions].
static Map<Type, Action<Intent>> defaultActions = <Type, Action<Intent>>{
DoNothingIntent: DoNothingAction(),
+ DoNothingAndStopPropagationIntent: DoNothingAction(consumesKey: false),
RequestFocusIntent: RequestFocusAction(),
NextFocusIntent: NextFocusAction(),
PreviousFocusIntent: PreviousFocusAction(),
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 431ee70..cfb1f33 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -6960,6 +6960,25 @@
/// would be presented as a separate feature than the checkbox, and
/// the user would not be able to be sure that they were related.
///
+/// {@tool snippet}
+/// This snippet shows how to use [MergeSemantics] to merge the semantics of
+/// a [Checkbox] and [Text] widget.
+///
+/// ```dart
+/// MergeSemantics(
+/// child: Row(
+/// children: <Widget>[
+/// Checkbox(
+/// value: true,
+/// onChanged: (bool value) => null,
+/// ),
+/// const Text("Settings"),
+/// ],
+/// ),
+/// )
+/// ```
+/// {@end-tool}
+///
/// Be aware that if two nodes in the subtree have conflicting
/// semantics, the result may be nonsensical. For example, a subtree
/// with a checked checkbox and an unchecked checkbox will be
diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart
index 799880c..b6c823f 100644
--- a/packages/flutter/lib/src/widgets/dismissible.dart
+++ b/packages/flutter/lib/src/widgets/dismissible.dart
@@ -28,7 +28,7 @@
/// confirm or veto a dismiss gesture.
///
/// Used by [Dismissible.confirmDismiss].
-typedef ConfirmDismissCallback = Future<bool> Function(DismissDirection direction);
+typedef ConfirmDismissCallback = Future<bool?> Function(DismissDirection direction);
/// The direction in which a [Dismissible] can be dismissed.
enum DismissDirection {
@@ -120,7 +120,7 @@
/// If the returned Future<bool> completes true, then this widget will be
/// dismissed, otherwise it will be moved back to its original location.
///
- /// If the returned Future<bool> completes to false or null the [onResize]
+ /// If the returned Future<bool?> completes to false or null the [onResize]
/// and [onDismissed] callbacks will not run.
final ConfirmDismissCallback? confirmDismiss;
@@ -458,7 +458,7 @@
updateKeepAlive();
}
- Future<bool> _confirmStartResizeAnimation() async {
+ Future<bool?> _confirmStartResizeAnimation() async {
if (widget.confirmDismiss != null) {
final DismissDirection direction = _dismissDirection!;
assert(direction != null);
diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart
index e473cc3..4c224ba 100644
--- a/packages/flutter/lib/src/widgets/focus_manager.dart
+++ b/packages/flutter/lib/src/widgets/focus_manager.dart
@@ -31,11 +31,32 @@
return true;
}
+/// An enum that describes how to handle a key event handled by a
+/// [FocusOnKeyCallback].
+enum KeyEventResult {
+ /// The key event has been handled, and the event should not be propagated to
+ /// other key event handlers.
+ handled,
+ /// The key event has not been handled, and the event should continue to be
+ /// propagated to other key event handlers, even non-Flutter ones.
+ ignored,
+ /// The key event has not been handled, but the key event should not be
+ /// propagated to other key event handlers.
+ ///
+ /// It will be returned to the platform embedding to be propagated to text
+ /// fields and non-Flutter key event handlers on the platform.
+ skipRemainingHandlers,
+}
+
/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
/// to receive key events.
///
/// The [node] is the node that received the event.
-typedef FocusOnKeyCallback = bool Function(FocusNode node, RawKeyEvent event);
+///
+/// Returns a [KeyEventResult] that describes how, and whether, the key event
+/// was handled.
+// TODO(gspencergoog): Convert this from dynamic to KeyEventResult once migration is complete.
+typedef FocusOnKeyCallback = dynamic Function(FocusNode node, RawKeyEvent event);
/// An attachment point for a [FocusNode].
///
@@ -321,7 +342,7 @@
/// }
/// }
///
-/// bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
+/// KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
/// if (event is RawKeyDownEvent) {
/// print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
/// if (event.logicalKey == LogicalKeyboardKey.keyR) {
@@ -329,22 +350,22 @@
/// setState(() {
/// _color = Colors.red;
/// });
-/// return true;
+/// return KeyEventResult.handled;
/// } else if (event.logicalKey == LogicalKeyboardKey.keyG) {
/// print('Changing color to green.');
/// setState(() {
/// _color = Colors.green;
/// });
-/// return true;
+/// return KeyEventResult.handled;
/// } else if (event.logicalKey == LogicalKeyboardKey.keyB) {
/// print('Changing color to blue.');
/// setState(() {
/// _color = Colors.blue;
/// });
-/// return true;
+/// return KeyEventResult.handled;
/// }
/// }
-/// return false;
+/// return KeyEventResult.ignored;
/// }
///
/// @override
@@ -1613,17 +1634,43 @@
_updateHighlightMode();
assert(_focusDebug('Received key event ${event.logicalKey}'));
- // Walk the current focus from the leaf to the root, calling each one's
- // onKey on the way up, and if one responds that they handled it, stop.
if (_primaryFocus == null) {
assert(_focusDebug('No primary focus for key event, ignored: $event'));
return false;
}
+
+ // Walk the current focus from the leaf to the root, calling each one's
+ // onKey on the way up, and if one responds that they handled it or want to
+ // stop propagation, stop.
bool handled = false;
for (final FocusNode node in <FocusNode>[_primaryFocus!, ..._primaryFocus!.ancestors]) {
- if (node.onKey != null && node.onKey!(node, event)) {
- assert(_focusDebug('Node $node handled key event $event.'));
- handled = true;
+ if (node.onKey != null) {
+ // TODO(gspencergoog): Convert this from dynamic to KeyEventResult once migration is complete.
+ final dynamic result = node.onKey!(node, event);
+ assert(result is bool || result is KeyEventResult,
+ 'Value returned from onKey handler must be a non-null bool or KeyEventResult, not ${result.runtimeType}');
+ if (result is KeyEventResult) {
+ switch (result) {
+ case KeyEventResult.handled:
+ assert(_focusDebug('Node $node handled key event $event.'));
+ handled = true;
+ break;
+ case KeyEventResult.skipRemainingHandlers:
+ assert(_focusDebug('Node $node stopped key event propagation: $event.'));
+ handled = false;
+ break;
+ case KeyEventResult.ignored:
+ continue;
+ }
+ } else if (result is bool){
+ if (result) {
+ assert(_focusDebug('Node $node handled key event $event.'));
+ handled = true;
+ break;
+ } else {
+ continue;
+ }
+ }
break;
}
}
diff --git a/packages/flutter/lib/src/widgets/focus_scope.dart b/packages/flutter/lib/src/widgets/focus_scope.dart
index 4a95ab7..b37d1c1 100644
--- a/packages/flutter/lib/src/widgets/focus_scope.dart
+++ b/packages/flutter/lib/src/widgets/focus_scope.dart
@@ -60,7 +60,7 @@
/// ```dart
/// Color _color = Colors.white;
///
-/// bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
+/// KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
/// if (event is RawKeyDownEvent) {
/// print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
/// if (event.logicalKey == LogicalKeyboardKey.keyR) {
@@ -68,22 +68,22 @@
/// setState(() {
/// _color = Colors.red;
/// });
-/// return true;
+/// return KeyEventResult.handled;
/// } else if (event.logicalKey == LogicalKeyboardKey.keyG) {
/// print('Changing color to green.');
/// setState(() {
/// _color = Colors.green;
/// });
-/// return true;
+/// return KeyEventResult.handled;
/// } else if (event.logicalKey == LogicalKeyboardKey.keyB) {
/// print('Changing color to blue.');
/// setState(() {
/// _color = Colors.blue;
/// });
-/// return true;
+/// return KeyEventResult.handled;
/// }
/// }
-/// return false;
+/// return KeyEventResult.ignored;
/// }
///
/// @override
diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart
index f7377cd..572beb1 100644
--- a/packages/flutter/lib/src/widgets/heroes.dart
+++ b/packages/flutter/lib/src/widgets/heroes.dart
@@ -300,7 +300,7 @@
// the Hero is inside a nested Navigator and should only be
// considered for animation if it is part of the top-most route in
// that nested Navigator and if that route is also a PageRoute.
- final ModalRoute<Object>? heroRoute = ModalRoute.of(hero);
+ final ModalRoute<Object?>? heroRoute = ModalRoute.of(hero);
if (heroRoute != null && heroRoute is PageRoute && heroRoute.isCurrent) {
inviteHero(hero, tag);
}
diff --git a/packages/flutter/lib/src/widgets/localizations.dart b/packages/flutter/lib/src/widgets/localizations.dart
index ef61693..8de4803 100644
--- a/packages/flutter/lib/src/widgets/localizations.dart
+++ b/packages/flutter/lib/src/widgets/localizations.dart
@@ -450,7 +450,7 @@
assert(context != null);
assert(type != null);
final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
- return scope?.localizationsState.resourcesFor<T>(type);
+ return scope?.localizationsState.resourcesFor<T?>(type);
}
@override
diff --git a/packages/flutter/lib/src/widgets/modal_barrier.dart b/packages/flutter/lib/src/widgets/modal_barrier.dart
index c4d3ce3..3d1b693 100644
--- a/packages/flutter/lib/src/widgets/modal_barrier.dart
+++ b/packages/flutter/lib/src/widgets/modal_barrier.dart
@@ -159,7 +159,7 @@
///
/// * [ModalRoute.barrierColor], which controls this property for the
/// [AnimatedModalBarrier] built by [ModalRoute] pages.
- Animation<Color> get color => listenable as Animation<Color>;
+ Animation<Color?> get color => listenable as Animation<Color?>;
/// Whether touching the barrier will pop the current route off the [Navigator].
///
diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart
index 33ad7a6..6884e89 100644
--- a/packages/flutter/lib/src/widgets/navigator.dart
+++ b/packages/flutter/lib/src/widgets/navigator.dart
@@ -306,8 +306,8 @@
/// The future completes with the value given to [Navigator.pop], if any, or
/// else the value of [currentResult]. See [didComplete] for more discussion
/// on this topic.
- Future<T> get popped => _popCompleter.future;
- final Completer<T> _popCompleter = Completer<T>();
+ Future<T?> get popped => _popCompleter.future;
+ final Completer<T?> _popCompleter = Completer<T?>();
/// A request was made to pop this route. If the route can handle it
/// internally (e.g. because it has its own stack of internal state) then
@@ -329,7 +329,7 @@
/// See [popped], [didComplete], and [currentResult] for a discussion of the
/// `result` argument.
@mustCallSuper
- bool didPop(T result) {
+ bool didPop(T? result) {
didComplete(result);
return true;
}
@@ -351,7 +351,7 @@
/// iOS-style back gesture. See [NavigatorState.didStartUserGesture].
@protected
@mustCallSuper
- void didComplete(T result) {
+ void didComplete(T? result) {
_popCompleter.complete(result ?? currentResult);
}
@@ -1693,7 +1693,7 @@
/// * [restorablePushNamed], which pushes a route that can be restored
/// during state restoration.
@optionalTypeArgs
- static Future<T> pushNamed<T extends Object?>(
+ static Future<T?> pushNamed<T extends Object?>(
BuildContext context,
String routeName, {
Object? arguments,
@@ -1810,7 +1810,7 @@
/// * [restorablePushReplacementNamed], which pushes a replacement route that
/// can be restored during state restoration.
@optionalTypeArgs
- static Future<T> pushReplacementNamed<T extends Object?, TO extends Object?>(
+ static Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
BuildContext context,
String routeName, {
TO? result,
@@ -1904,7 +1904,7 @@
/// * [restorablePopAndPushNamed], which pushes a new route that can be
/// restored during state restoration.
@optionalTypeArgs
- static Future<T> popAndPushNamed<T extends Object?, TO extends Object?>(
+ static Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
BuildContext context,
String routeName, {
TO? result,
@@ -2008,7 +2008,7 @@
/// * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
/// be restored during state restoration.
@optionalTypeArgs
- static Future<T> pushNamedAndRemoveUntil<T extends Object?>(
+ static Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
BuildContext context,
String newRouteName,
RoutePredicate predicate, {
@@ -2087,7 +2087,7 @@
/// * [restorablePush], which pushes a route that can be restored during
/// state restoration.
@optionalTypeArgs
- static Future<T> push<T extends Object?>(BuildContext context, Route<T> route) {
+ static Future<T?> push<T extends Object?>(BuildContext context, Route<T> route) {
return Navigator.of(context)!.push(route);
}
@@ -2190,7 +2190,7 @@
/// * [restorablePushReplacement], which pushes a replacement route that can
/// be restored during state restoration.
@optionalTypeArgs
- static Future<T> pushReplacement<T extends Object?, TO extends Object?>(BuildContext context, Route<T> newRoute, { TO? result }) {
+ static Future<T?> pushReplacement<T extends Object?, TO extends Object?>(BuildContext context, Route<T> newRoute, { TO? result }) {
return Navigator.of(context)!.pushReplacement<T, TO>(newRoute, result: result);
}
@@ -2295,7 +2295,7 @@
/// * [restorablePushAndRemoveUntil], which pushes a route that can be
/// restored during state restoration.
@optionalTypeArgs
- static Future<T> pushAndRemoveUntil<T extends Object?>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) {
+ static Future<T?> pushAndRemoveUntil<T extends Object?>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) {
return Navigator.of(context)!.pushAndRemoveUntil<T>(newRoute, predicate);
}
@@ -2876,7 +2876,7 @@
// User-provided restoration ids of Pages are prefixed with 'p+'. Generated
// ids for pageless routes are prefixed with 'r+' to avoid clashes.
if (hasPage) {
- final Page<Object> page = route.settings as Page<Object>;
+ final Page<Object?> page = route.settings as Page<Object?>;
return page.restorationId != null ? 'p+${page.restorationId}' : null;
}
if (restorationInformation != null) {
@@ -4000,7 +4000,7 @@
/// * [restorablePushNamed], which pushes a route that can be restored
/// during state restoration.
@optionalTypeArgs
- Future<T> pushNamed<T extends Object?>(
+ Future<T?> pushNamed<T extends Object?>(
String routeName, {
Object? arguments,
}) {
@@ -4067,7 +4067,7 @@
/// * [restorablePushReplacementNamed], which pushes a replacement route that
/// can be restored during state restoration.
@optionalTypeArgs
- Future<T> pushReplacementNamed<T extends Object?, TO extends Object?>(
+ Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
String routeName, {
TO? result,
Object? arguments,
@@ -4137,7 +4137,7 @@
/// * [restorablePopAndPushNamed], which pushes a new route that can be
/// restored during state restoration.
@optionalTypeArgs
- Future<T> popAndPushNamed<T extends Object?, TO extends Object?>(
+ Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
String routeName, {
TO? result,
Object? arguments,
@@ -4200,7 +4200,7 @@
/// * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
/// be restored during state restoration.
@optionalTypeArgs
- Future<T> pushNamedAndRemoveUntil<T extends Object?>(
+ Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
String newRouteName,
RoutePredicate predicate, {
Object? arguments,
@@ -4266,7 +4266,7 @@
/// * [restorablePush], which pushes a route that can be restored during
/// state restoration.
@optionalTypeArgs
- Future<T> push<T extends Object?>(Route<T> route) {
+ Future<T?> push<T extends Object?>(Route<T> route) {
_pushEntry(_RouteEntry(route, initialState: _RouteLifecycle.push));
return route.popped;
}
@@ -4410,7 +4410,7 @@
/// * [restorablePushReplacement], which pushes a replacement route that can
/// be restored during state restoration.
@optionalTypeArgs
- Future<T> pushReplacement<T extends Object?, TO extends Object?>(Route<T> newRoute, { TO? result }) {
+ Future<T?> pushReplacement<T extends Object?, TO extends Object?>(Route<T> newRoute, { TO? result }) {
assert(newRoute != null);
assert(newRoute._navigator == null);
_pushReplacementEntry(_RouteEntry(newRoute, initialState: _RouteLifecycle.pushReplace), result);
@@ -4516,7 +4516,7 @@
/// * [restorablePushAndRemoveUntil], which pushes a route that can be
/// restored during state restoration.
@optionalTypeArgs
- Future<T> pushAndRemoveUntil<T extends Object?>(Route<T> newRoute, RoutePredicate predicate) {
+ Future<T?> pushAndRemoveUntil<T extends Object?>(Route<T> newRoute, RoutePredicate predicate) {
assert(newRoute != null);
assert(newRoute._navigator == null);
assert(newRoute.overlayEntries.isEmpty);
@@ -5116,9 +5116,9 @@
factory _RestorationInformation.fromSerializableData(Object data) {
assert(data != null);
- final List<Object> casted = data as List<Object>;
+ final List<Object?> casted = data as List<Object?>;
assert(casted.isNotEmpty);
- final _RouteRestorationType type = _RouteRestorationType.values[casted[0] as int];
+ final _RouteRestorationType type = _RouteRestorationType.values[casted[0]! as int];
switch (type) {
case _RouteRestorationType.named:
return _NamedRestorationInformation.fromSerializableData(casted.sublist(1));
@@ -5167,11 +5167,11 @@
required this.restorationScopeId,
}) : assert(name != null), super(_RouteRestorationType.named);
- factory _NamedRestorationInformation.fromSerializableData(List<Object> data) {
+ factory _NamedRestorationInformation.fromSerializableData(List<Object?> data) {
assert(data.length >= 2);
return _NamedRestorationInformation(
- restorationScopeId: data[0] as int,
- name: data[1] as String,
+ restorationScopeId: data[0]! as int,
+ name: data[1]! as String,
arguments: data.length > 2 ? data[2] : null,
);
}
@@ -5206,11 +5206,11 @@
required this.restorationScopeId,
}) : assert(routeBuilder != null), super(_RouteRestorationType.anonymous);
- factory _AnonymousRestorationInformation.fromSerializableData(List<Object> data) {
+ factory _AnonymousRestorationInformation.fromSerializableData(List<Object?> data) {
assert(data.length > 1);
- final RestorableRouteBuilder routeBuilder = ui.PluginUtilities.getCallbackFromHandle(ui.CallbackHandle.fromRawHandle(data[1] as int))! as RestorableRouteBuilder;
+ final RestorableRouteBuilder routeBuilder = ui.PluginUtilities.getCallbackFromHandle(ui.CallbackHandle.fromRawHandle(data[1]! as int))! as RestorableRouteBuilder;
return _AnonymousRestorationInformation(
- restorationScopeId: data[0] as int,
+ restorationScopeId: data[0]! as int,
routeBuilder: routeBuilder,
arguments: data.length > 2 ? data[2] : null,
);
@@ -5383,10 +5383,10 @@
}
@override
- Map<String?, List<Object>>? fromPrimitives(Object data) {
- final Map<dynamic, dynamic> casted = data as Map<dynamic, dynamic>;
- return casted.map<String, List<Object>>((dynamic key, dynamic value) => MapEntry<String, List<Object>>(
- key as String,
+ Map<String?, List<Object>>? fromPrimitives(Object? data) {
+ final Map<dynamic, dynamic> casted = data! as Map<dynamic, dynamic>;
+ return casted.map<String?, List<Object>>((dynamic key, dynamic value) => MapEntry<String?, List<Object>>(
+ key as String?,
List<Object>.from(value as List<dynamic>, growable: true),
));
}
@@ -5694,9 +5694,9 @@
}
@override
- String fromPrimitives(Object data) {
+ String fromPrimitives(Object? data) {
assert(data != null);
- return data as String;
+ return data! as String;
}
bool _disposed = false;
diff --git a/packages/flutter/lib/src/widgets/restoration.dart b/packages/flutter/lib/src/widgets/restoration.dart
index e83c6ed..9fc259f 100644
--- a/packages/flutter/lib/src/widgets/restoration.dart
+++ b/packages/flutter/lib/src/widgets/restoration.dart
@@ -425,7 +425,7 @@
/// [RestorableProperty]. Whenever new restoration data has been provided to
/// the [RestorationMixin] the property is registered to, either this method
/// or [createDefaultValue] is called before [initWithValue] is invoked.
- T fromPrimitives(Object data);
+ T fromPrimitives(Object? data);
/// Called by the [RestorationMixin] with the `value` returned by either
/// [createDefaultValue] or [fromPrimitives] to set the value that this
@@ -884,7 +884,7 @@
/// restore the internal state of a [State] object, it may be removed from the
/// restoration data by calling this method.
@protected
- void unregisterFromRestoration(RestorableProperty<Object> property) {
+ void unregisterFromRestoration(RestorableProperty<Object?> property) {
assert(property != null);
assert(property._owner == this);
_bucket?.remove<Object?>(property._restorationId!);
diff --git a/packages/flutter/lib/src/widgets/restoration_properties.dart b/packages/flutter/lib/src/widgets/restoration_properties.dart
index 430e6cb..3a7221e 100644
--- a/packages/flutter/lib/src/widgets/restoration_properties.dart
+++ b/packages/flutter/lib/src/widgets/restoration_properties.dart
@@ -173,9 +173,9 @@
}
@override
- T fromPrimitives(Object serialized) {
+ T fromPrimitives(Object? serialized) {
assert(serialized != null);
- return serialized as T;
+ return serialized! as T;
}
@override
@@ -360,8 +360,8 @@
}
@override
- TextEditingController fromPrimitives(Object data) {
- return TextEditingController(text: data as String);
+ TextEditingController fromPrimitives(Object? data) {
+ return TextEditingController(text: data! as String);
}
@override
diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart
index a91d8d3..cf136f8 100644
--- a/packages/flutter/lib/src/widgets/routes.dart
+++ b/packages/flutter/lib/src/widgets/routes.dart
@@ -62,7 +62,7 @@
bool get finishedWhenPopped => true;
@override
- bool didPop(T result) {
+ bool didPop(T? result) {
final bool returnValue = super.didPop(result);
assert(returnValue);
if (finishedWhenPopped)
@@ -90,8 +90,8 @@
/// This future completes once the animation has been dismissed. That will be
/// after [popped], because [popped] typically completes before the animation
/// even starts, as soon as the route is popped.
- Future<T> get completed => _transitionCompleter.future;
- final Completer<T> _transitionCompleter = Completer<T>();
+ Future<T?> get completed => _transitionCompleter.future;
+ final Completer<T?> _transitionCompleter = Completer<T?>();
/// {@template flutter.widgets.transitionRoute.transitionDuration}
/// The duration the transition going forwards.
@@ -235,7 +235,7 @@
}
@override
- bool didPop(T result) {
+ bool didPop(T? result) {
assert(_controller != null, '$runtimeType.didPop called before calling install() or after calling dispose().');
assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
_result = result;
@@ -631,7 +631,7 @@
}
@override
- bool didPop(T result) {
+ bool didPop(T? result) {
if (_localHistory != null && _localHistory!.isNotEmpty) {
final LocalHistoryEntry entry = _localHistory!.removeLast();
assert(entry._owner == this);
@@ -1818,7 +1818,7 @@
///
/// * [showDialog], which displays a Material-style dialog.
/// * [showCupertinoDialog], which displays an iOS-style dialog.
-Future<T> showGeneralDialog<T extends Object?>({
+Future<T?> showGeneralDialog<T extends Object?>({
required BuildContext context,
required RoutePageBuilder pageBuilder,
bool barrierDismissible = false,
diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart
index d6ffbc3..8b3d819 100644
--- a/packages/flutter/lib/src/widgets/scrollable.dart
+++ b/packages/flutter/lib/src/widgets/scrollable.dart
@@ -1084,8 +1084,8 @@
}
@override
- double fromPrimitives(Object data) {
- return data as double;
+ double fromPrimitives(Object? data) {
+ return data! as double;
}
@override
diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart
index c0a1ee5..8754301 100644
--- a/packages/flutter/lib/src/widgets/shortcuts.dart
+++ b/packages/flutter/lib/src/widgets/shortcuts.dart
@@ -264,9 +264,13 @@
/// True if the [ShortcutManager] should not pass on keys that it doesn't
/// handle to any key-handling widgets that are ancestors to this one.
///
- /// Setting [modal] to true is the equivalent of always handling any key given
- /// to it, even if that key doesn't appear in the [shortcuts] map. Keys that
- /// don't appear in the map will be dropped.
+ /// Setting [modal] to true will prevent any key event given to this manager
+ /// from being given to any ancestor managers, even if that key doesn't appear
+ /// in the [shortcuts] map.
+ ///
+ /// The net effect of setting `modal` to true is to return
+ /// [KeyEventResult.skipRemainingHandlers] from [handleKeypress] if it does not
+ /// exist in the shortcut map, instead of returning [KeyEventResult.ignored].
final bool modal;
/// Returns the shortcut map.
@@ -284,68 +288,91 @@
}
}
- /// Handles a key pressed `event` in the given `context`.
+ /// Returns the [Intent], if any, that matches the current set of pressed
+ /// keys.
///
- /// The optional `keysPressed` argument provides an override to keys that the
- /// [RawKeyboard] reports. If not specified, uses [RawKeyboard.keysPressed]
- /// instead.
+ /// Returns null if no intent matches the current set of pressed keys.
///
- /// If a key mapping is found, then the associated action will be invoked
- /// using the [Intent] that the [LogicalKeySet] maps to, and the currently
- /// focused widget's context (from [FocusManager.primaryFocus]).
- ///
- /// The object returned is the result of [Action.invoke] being called on the
- /// [Action] bound to the [Intent] that the key press maps to, or null, if the
- /// key press didn't match any intent.
- @protected
- bool handleKeypress(
- BuildContext context,
- RawKeyEvent event, {
- LogicalKeySet? keysPressed,
- }) {
- if (event is! RawKeyDownEvent) {
- return false;
+ /// Defaults to a set derived from [RawKeyboard.keysPressed] if `keysPressed` is
+ /// not supplied.
+ Intent? _find({ LogicalKeySet? keysPressed }) {
+ if (keysPressed == null && RawKeyboard.instance.keysPressed.isEmpty) {
+ return null;
}
- assert(context != null);
- LogicalKeySet? keySet = keysPressed;
- if (keySet == null) {
- assert(RawKeyboard.instance.keysPressed.isNotEmpty,
- 'Received a key down event when no keys are in keysPressed. '
- "This state can occur if the key event being sent doesn't properly "
- 'set its modifier flags. This was the event: $event and its data: '
- '${event.data}');
- // Avoid the crash in release mode, since it's easy to miss a particular
- // bad key sequence in testing, and so shouldn't crash the app in release.
- if (RawKeyboard.instance.keysPressed.isNotEmpty) {
- keySet = LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed);
- } else {
- return false;
- }
- }
- Intent? matchedIntent = _shortcuts[keySet];
+ keysPressed ??= LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed);
+ Intent? matchedIntent = _shortcuts[keysPressed];
if (matchedIntent == null) {
// If there's not a more specific match, We also look for any keys that
// have synonyms in the map. This is for things like left and right shift
// keys mapping to just the "shift" pseudo-key.
final Set<LogicalKeyboardKey> pseudoKeys = <LogicalKeyboardKey>{};
- for (final LogicalKeyboardKey setKey in keySet.keys) {
- final Set<LogicalKeyboardKey> synonyms = setKey.synonyms;
- if (synonyms.isNotEmpty) {
- // There currently aren't any synonyms that match more than one key.
- pseudoKeys.add(synonyms.first);
- } else {
- pseudoKeys.add(setKey);
+ for (final KeyboardKey setKey in keysPressed.keys) {
+ if (setKey is LogicalKeyboardKey) {
+ final Set<LogicalKeyboardKey> synonyms = setKey.synonyms;
+ if (synonyms.isNotEmpty) {
+ // There currently aren't any synonyms that match more than one key.
+ assert(synonyms.length == 1, 'Unexpectedly encountered a key synonym with more than one key.');
+ pseudoKeys.add(synonyms.first);
+ } else {
+ pseudoKeys.add(setKey);
+ }
}
}
matchedIntent = _shortcuts[LogicalKeySet.fromSet(pseudoKeys)];
}
- if (matchedIntent != null) {
- final BuildContext? primaryContext = primaryFocus?.context;
- assert (primaryContext != null);
- Actions.invoke(primaryContext!, matchedIntent, nullOk: true);
- return true;
+ return matchedIntent;
+ }
+
+ /// Handles a key press `event` in the given `context`.
+ ///
+ /// The optional `keysPressed` argument is used as the set of currently
+ /// pressed keys. Defaults to a set derived from [RawKeyboard.keysPressed] if
+ /// `keysPressed` is not supplied.
+ ///
+ /// If a key mapping is found, then the associated action will be invoked
+ /// using the [Intent] that the `keysPressed` maps to, and the currently
+ /// focused widget's context (from [FocusManager.primaryFocus]).
+ ///
+ /// Returns a [KeyEventResult.handled] if an action was invoked, otherwise a
+ /// [KeyEventResult.skipRemainingHandlers] if [modal] is true, or if it maps to a
+ /// [DoNothingAction] with [DoNothingAction.consumesKey] set to false, and
+ /// in all other cases returns [KeyEventResult.ignored].
+ ///
+ /// In order for an action to be invoked (and [KeyEventResult.handled]
+ /// returned), a pressed [KeySet] must be mapped to an [Intent], the [Intent]
+ /// must be mapped to an [Action], and the [Action] must be enabled.
+ @protected
+ KeyEventResult handleKeypress(
+ BuildContext context,
+ RawKeyEvent event, {
+ LogicalKeySet? keysPressed,
+ }) {
+ if (event is! RawKeyDownEvent) {
+ return KeyEventResult.ignored;
}
- return false;
+ assert(context != null);
+ assert(keysPressed != null || RawKeyboard.instance.keysPressed.isNotEmpty,
+ 'Received a key down event when no keys are in keysPressed. '
+ "This state can occur if the key event being sent doesn't properly "
+ 'set its modifier flags. This was the event: $event and its data: '
+ '${event.data}');
+ final Intent? matchedIntent = _find(keysPressed: keysPressed);
+ if (matchedIntent != null) {
+ final BuildContext primaryContext = primaryFocus!.context!;
+ assert (primaryContext != null);
+ final Action<Intent>? action = Actions.find<Intent>(
+ primaryContext,
+ intent: matchedIntent,
+ nullOk: true,
+ );
+ if (action != null && action.isEnabled(matchedIntent)) {
+ Actions.of(primaryContext).invokeAction(action, matchedIntent, primaryContext);
+ return action.consumesKey(matchedIntent)
+ ? KeyEventResult.handled
+ : KeyEventResult.skipRemainingHandlers;
+ }
+ }
+ return modal ? KeyEventResult.skipRemainingHandlers : KeyEventResult.ignored;
}
@override
@@ -433,7 +460,7 @@
}
return true;
}());
- return inherited?.notifier;
+ return inherited?.manager;
}
@override
@@ -480,11 +507,11 @@
manager.shortcuts = widget.shortcuts;
}
- bool _handleOnKey(FocusNode node, RawKeyEvent event) {
+ KeyEventResult _handleOnKey(FocusNode node, RawKeyEvent event) {
if (node.context == null) {
- return false;
+ return KeyEventResult.ignored;
}
- return manager.handleKeypress(node.context!, event) || manager.modal;
+ return manager.handleKeypress(node.context!, event);
}
@override
@@ -508,4 +535,6 @@
}) : assert(manager != null),
assert(child != null),
super(notifier: manager, child: child);
+
+ ShortcutManager get manager => super.notifier!;
}
diff --git a/packages/flutter/lib/src/widgets/table.dart b/packages/flutter/lib/src/widgets/table.dart
index d3e42af..ec60a50 100644
--- a/packages/flutter/lib/src/widgets/table.dart
+++ b/packages/flutter/lib/src/widgets/table.dart
@@ -303,16 +303,16 @@
}
@override
- void insertRenderObjectChild(RenderObject child, IndexedSlot<Element>? slot) {
+ void insertRenderObjectChild(RenderObject child, dynamic slot) {
renderObject.setupParentData(child);
}
@override
- void moveRenderObjectChild(RenderObject child, IndexedSlot<Element>? oldSlot, IndexedSlot<Element>? newSlot) {
+ void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
}
@override
- void removeRenderObjectChild(RenderObject child, IndexedSlot<Element>? slot) {
+ void removeRenderObjectChild(RenderObject child, dynamic slot) {
final TableCellParentData childParentData = child.parentData! as TableCellParentData;
renderObject.setChild(childParentData.x!, childParentData.y!, null);
}
diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart
index b7a96a5..194a88e 100644
--- a/packages/flutter/lib/src/widgets/widget_inspector.dart
+++ b/packages/flutter/lib/src/widgets/widget_inspector.dart
@@ -880,17 +880,20 @@
registerServiceExtension(
name: name,
callback: (Map<String, String> parameters) async {
- const String argPrefix = 'arg';
final List<String> args = <String>[];
- parameters.forEach((String name, String value) {
- if (name.startsWith(argPrefix)) {
- final int index = int.parse(name.substring(argPrefix.length));
- if (index >= args.length) {
- args.length = index + 1;
- }
- args[index] = value;
+ int index = 0;
+ while (true) {
+ final String name = 'arg$index';
+ if (parameters.containsKey(name)) {
+ args.add(parameters[name]!);
+ } else {
+ break;
}
- });
+ index++;
+ }
+ // Verify that the only arguments other than perhaps 'isolateId' are
+ // arguments we have already handled.
+ assert(index == parameters.length || (index == parameters.length - 1 && parameters.containsKey('isolateId')));
return <String, Object?>{'result': await callback(args)};
},
);
diff --git a/packages/flutter/test/animation/animation_controller_test.dart b/packages/flutter/test/animation/animation_controller_test.dart
index e634487..29bdbec 100644
--- a/packages/flutter/test/animation/animation_controller_test.dart
+++ b/packages/flutter/test/animation/animation_controller_test.dart
@@ -253,6 +253,29 @@
largeRangeController.stop();
});
+ test('Custom springDescription can be applied', () {
+ final AnimationController controller = AnimationController(
+ vsync: const TestVSync(),
+ );
+ final AnimationController customSpringController = AnimationController(
+ vsync: const TestVSync(),
+ );
+
+ controller.fling();
+ // Will produce longer and smoother animation than the default.
+ customSpringController.fling(
+ springDescription: SpringDescription.withDampingRatio(
+ mass: 0.01,
+ stiffness: 10.0,
+ ratio: 2.0,
+ ),
+ );
+ tick(const Duration(milliseconds: 0));
+ tick(const Duration(milliseconds: 50));
+
+ expect(customSpringController.value < controller.value, true);
+ });
+
test('lastElapsedDuration control test', () {
final AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 100),
diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
index f7f46b2..dfa955d 100644
--- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart
+++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
@@ -135,11 +135,11 @@
// place.
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).first),
- const Offset(337.0234375, 13.5),
+ const Offset(337.1953125, 13.5),
);
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).last),
- const Offset(337.0234375, 13.5),
+ const Offset(337.1953125, 13.5),
);
});
@@ -156,11 +156,11 @@
// Same as LTR but more to the right now.
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).first),
- const Offset(362.9765625, 13.5),
+ const Offset(362.8046875, 13.5),
);
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).last),
- const Offset(362.9765625, 13.5),
+ const Offset(362.8046875, 13.5),
);
});
@@ -329,7 +329,7 @@
expect(bottomMiddle.text.style!.color, const Color(0xff00050a));
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).first),
- const Offset(337.0234375, 13.5),
+ const Offset(337.1953125, 13.5),
);
// The top back label is styled exactly the same way. But the opacity tweens
@@ -339,7 +339,7 @@
expect(topBackLabel.text.style!.color, const Color(0xff00050a));
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).last),
- const Offset(337.0234375, 13.5),
+ const Offset(337.1953125, 13.5),
);
}
@@ -374,7 +374,7 @@
expect(bottomMiddle.text.style!.color, const Color(0xff00050a));
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).first),
- const Offset(362.9765625, 13.5),
+ const Offset(362.8046875, 13.5),
);
// The top back label is styled exactly the same way. But the opacity tweens
@@ -384,7 +384,7 @@
expect(topBackLabel.text.style!.color, const Color(0xff00050a));
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).last),
- const Offset(362.9765625, 13.5),
+ const Offset(362.8046875, 13.5),
);
}
@@ -599,12 +599,12 @@
// Come in from the right and fade in.
checkOpacity(tester, backChevron, 0.0);
expect(
- tester.getTopLeft(backChevron), const Offset(73.078125, 5.0));
+ tester.getTopLeft(backChevron), const Offset(86.734375, 7.0));
await tester.pump(const Duration(milliseconds: 150));
checkOpacity(tester, backChevron, 0.09497911669313908);
expect(
- tester.getTopLeft(backChevron), const Offset(23.260527312755585, 5.0));
+ tester.getTopLeft(backChevron), const Offset(31.055883467197418, 7.0));
});
testWidgets('First appearance of back chevron fades in from the left in RTL', (WidgetTester tester) async {
@@ -643,14 +643,14 @@
checkOpacity(tester, backChevron, 0.0);
expect(
tester.getTopRight(backChevron),
- const Offset(692.921875, 5.0),
+ const Offset(687.265625, 7.0),
);
await tester.pump(const Duration(milliseconds: 150));
checkOpacity(tester, backChevron, 0.09497911669313908);
expect(
tester.getTopRight(backChevron),
- const Offset(742.7394726872444, 5.0),
+ const Offset(742.9441165328026, 7.0),
);
});
@@ -670,15 +670,15 @@
checkOpacity(tester, backChevrons.first, 0.8833301812410355);
checkOpacity(tester, backChevrons.last, 0.0);
// Both overlap at the same place.
- expect(tester.getTopLeft(backChevrons.first), const Offset(8.0, 5.0));
- expect(tester.getTopLeft(backChevrons.last), const Offset(8.0, 5.0));
+ expect(tester.getTopLeft(backChevrons.first), const Offset(14.0, 7.0));
+ expect(tester.getTopLeft(backChevrons.last), const Offset(14.0, 7.0));
await tester.pump(const Duration(milliseconds: 150));
checkOpacity(tester, backChevrons.first, 0.0);
checkOpacity(tester, backChevrons.last, 0.4604858811944723);
// Still in the same place.
- expect(tester.getTopLeft(backChevrons.first), const Offset(8.0, 5.0));
- expect(tester.getTopLeft(backChevrons.last), const Offset(8.0, 5.0));
+ expect(tester.getTopLeft(backChevrons.first), const Offset(14.0, 7.0));
+ expect(tester.getTopLeft(backChevrons.last), const Offset(14.0, 7.0));
});
testWidgets('Bottom middle just fades if top page has a custom leading', (WidgetTester tester) async {
@@ -789,14 +789,14 @@
checkOpacity(tester, flying(tester, find.text('Page 1')), 0.6697911769151688);
expect(
tester.getTopLeft(flying(tester, find.text('Page 1'))),
- const Offset(30.8125, 13.5),
+ const Offset(34.8125, 13.5),
);
await tester.pump(const Duration(milliseconds: 150));
checkOpacity(tester, flying(tester, find.text('Page 1')), 0.0);
expect(
tester.getTopLeft(flying(tester, find.text('Page 1'))),
- const Offset(-262.2321922779083, 13.5),
+ const Offset(-258.2321922779083, 13.5),
);
});
@@ -826,7 +826,7 @@
checkOpacity(tester, flying(tester, find.text('Page 1')), 0.6697911769151688);
expect(
tester.getTopRight(flying(tester, find.text('Page 1'))),
- const Offset(769.1875, 13.5),
+ const Offset(765.1875, 13.5),
);
await tester.pump(const Duration(milliseconds: 150));
@@ -834,7 +834,7 @@
expect(
tester.getTopRight(flying(tester, find.text('Page 1'))),
// >1000. It's now off the screen.
- const Offset(1062.2321922779083, 13.5),
+ const Offset(1058.2321922779083, 13.5),
);
});
@@ -856,11 +856,11 @@
checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.0);
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).first),
- const Offset(17.375, 52.39453125),
+ const Offset(17.546875, 52.39453125),
);
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).last),
- const Offset(17.375, 52.39453125),
+ const Offset(17.546875, 52.39453125),
);
await tester.pump(const Duration(milliseconds: 150));
@@ -868,11 +868,11 @@
checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.4604858811944723);
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).first),
- const Offset(40.818575382232666, 22.49655644595623),
+ const Offset(43.92089730501175, 22.49655644595623),
);
expect(
tester.getTopLeft(flying(tester, find.text('Page 1')).last),
- const Offset(40.818575382232666, 22.49655644595623),
+ const Offset(43.92089730501175, 22.49655644595623),
);
});
@@ -896,11 +896,11 @@
checkOpacity(tester, flying(tester, find.text('Back')), 0.0);
expect(
tester.getTopLeft(flying(tester, find.text('A title too long to fit'))),
- const Offset(17.375, 52.39453125),
+ const Offset(17.546875, 52.39453125),
);
expect(
tester.getTopLeft(flying(tester, find.text('Back'))),
- const Offset(17.375, 52.39453125),
+ const Offset(17.546875, 52.39453125),
);
await tester.pump(const Duration(milliseconds: 150));
@@ -908,11 +908,11 @@
checkOpacity(tester, flying(tester, find.text('Back')), 0.4604858811944723);
expect(
tester.getTopLeft(flying(tester, find.text('A title too long to fit'))),
- const Offset(40.818575382232666, 22.49655644595623),
+ const Offset(43.92089730501175, 22.49655644595623),
);
expect(
tester.getTopLeft(flying(tester, find.text('Back'))),
- const Offset(40.818575382232666, 22.49655644595623),
+ const Offset(43.92089730501175, 22.49655644595623),
);
});
diff --git a/packages/flutter/test/cupertino/route_test.dart b/packages/flutter/test/cupertino/route_test.dart
index bacea8f..0a9a8bd 100644
--- a/packages/flutter/test/cupertino/route_test.dart
+++ b/packages/flutter/test/cupertino/route_test.dart
@@ -154,8 +154,8 @@
// Also shows the previous page's title next to the back button.
expect(find.widgetWithText(CupertinoButton, 'An iPod'), findsOneWidget);
- // 2 paddings + 1 ahem character at font size 34.0.
- expect(tester.getTopLeft(find.text('An iPod')).dx, 8.0 + 34.0 + 6.0);
+ // 3 paddings + 1 ahem character at font size 34.0.
+ expect(tester.getTopLeft(find.text('An iPod')).dx, 8.0 + 4.0 + 34.0 + 6.0);
});
testWidgets('Previous title is correct on first transition frame', (WidgetTester tester) async {
@@ -261,7 +261,7 @@
// from An iPod to Back (since An Internet communicator is too long to
// fit in the back button).
expect(find.widgetWithText(CupertinoButton, 'Back'), findsOneWidget);
- expect(tester.getTopLeft(find.text('Back')).dx, 8.0 + 34.0 + 6.0);
+ expect(tester.getTopLeft(find.text('Back')).dx, 8.0 + 4.0 + 34.0 + 6.0);
});
testWidgets('Back swipe dismiss interrupted by route push', (WidgetTester tester) async {
diff --git a/packages/flutter/test/cupertino/slider_test.dart b/packages/flutter/test/cupertino/slider_test.dart
index 15a9623..2afed42 100644
--- a/packages/flutter/test/cupertino/slider_test.dart
+++ b/packages/flutter/test/cupertino/slider_test.dart
@@ -318,6 +318,7 @@
increasedValue: '60%',
decreasedValue: '40%',
textDirection: TextDirection.ltr,
+ flags: <SemanticsFlag>[SemanticsFlag.isSlider],
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
@@ -341,7 +342,14 @@
);
expect(semantics, hasSemantics(
- TestSemantics.root(),
+ TestSemantics.root(
+ children: <TestSemantics>[
+ TestSemantics(
+ id: 1,
+ flags: <SemanticsFlag>[SemanticsFlag.isSlider],
+ )
+ ],
+ ),
ignoreRect: true,
ignoreTransform: true,
));
@@ -365,6 +373,7 @@
);
expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics(
+ isSlider: true,
hasIncreaseAction: true,
hasDecreaseAction: true,
value: '50%',
@@ -387,6 +396,7 @@
);
expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics(
+ isSlider: true,
hasIncreaseAction: true,
hasDecreaseAction: true,
value: '60%',
diff --git a/packages/flutter/test/foundation/consolidate_response_test.dart b/packages/flutter/test/foundation/consolidate_response_test.dart
index b4775f9..b628813 100644
--- a/packages/flutter/test/foundation/consolidate_response_test.dart
+++ b/packages/flutter/test/foundation/consolidate_response_test.dart
@@ -111,7 +111,7 @@
test('Notifies onBytesReceived with gzipped numbers', () async {
response.contentLength = gzipped.length;
- final List<int?> records = <int>[];
+ final List<int?> records = <int?>[];
await consolidateHttpClientResponseBytes(
response,
onBytesReceived: (int cumulative, int? total) {
diff --git a/packages/flutter/test/foundation/diagnostics_json_test.dart b/packages/flutter/test/foundation/diagnostics_json_test.dart
index 77ad289..facc1c1 100644
--- a/packages/flutter/test/foundation/diagnostics_json_test.dart
+++ b/packages/flutter/test/foundation/diagnostics_json_test.dart
@@ -76,7 +76,7 @@
test('subtreeDepth 1', () {
final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate(subtreeDepth: 1));
expect(result.containsKey('properties'), isFalse);
- final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
expect(children[0].containsKey('children'), isFalse);
expect(children[1].containsKey('children'), isFalse);
expect(children[2].containsKey('children'), isFalse);
@@ -85,7 +85,7 @@
test('subtreeDepth 5', () {
final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate(subtreeDepth: 5));
expect(result.containsKey('properties'), isFalse);
- final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
expect(children[0]['children'], hasLength(0));
expect(children[1]['children'], hasLength(3));
expect(children[2]['children'], hasLength(0));
@@ -103,7 +103,7 @@
subtreeDepth: 1,
));
expect(result['properties'], hasLength(7));
- final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
expect(children, hasLength(3));
expect(children[0]['properties'], hasLength(0));
expect(children[1]['properties'], hasLength(2));
@@ -119,13 +119,13 @@
},
));
expect(result['foo'], isTrue);
- final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
expect(properties, hasLength(7));
- expect(properties.every((Map<String, Object> property) => property['foo'] == true), isTrue);
+ expect(properties.every((Map<String, Object?> property) => property['foo'] == true), isTrue);
- final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
expect(children, hasLength(3));
- expect(children.every((Map<String, Object> child) => child['foo'] == true), isTrue);
+ expect(children.every((Map<String, Object?> child) => child['foo'] == true), isTrue);
});
test('filterProperties - sublist', () {
@@ -135,9 +135,9 @@
return nodes.whereType<StringProperty>().toList();
},
));
- final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
expect(properties, hasLength(3));
- expect(properties.every((Map<String, Object> property) => property['type'] == 'StringProperty'), isTrue);
+ expect(properties.every((Map<String, Object?> property) => property['type'] == 'StringProperty'), isTrue);
});
test('filterProperties - replace', () {
@@ -154,7 +154,7 @@
];
},
));
- final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
expect(properties, hasLength(1));
expect(properties.single['name'], 'foo');
});
@@ -166,7 +166,7 @@
return nodes.where((DiagnosticsNode node) => node.getProperties().isEmpty).toList();
},
));
- final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
expect(children, hasLength(1));
});
@@ -177,7 +177,7 @@
return nodes.expand((DiagnosticsNode node) => node.getChildren()).toList();
},
));
- final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
expect(children, hasLength(3));
expect(children.first['name'], 'child node B1');
});
@@ -190,11 +190,11 @@
return nodes.take(2).toList();
},
));
- final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
expect(children, hasLength(3));
expect(children.last['truncated'], isTrue);
- final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
expect(properties, hasLength(3));
expect(properties.last['truncated'], isTrue);
});
@@ -207,13 +207,13 @@
return delegate.copyWith(includeProperties: false);
},
));
- final List<Map<String, Object>> properties = result['properties']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
expect(properties, hasLength(7));
- expect(properties.every((Map<String, Object> property) => !property.containsKey('properties')), isTrue);
+ expect(properties.every((Map<String, Object?> property) => !property.containsKey('properties')), isTrue);
- final List<Map<String, Object>> children = result['children']! as List<Map<String, Object>>;
+ final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
expect(children, hasLength(3));
- expect(children.every((Map<String, Object> child) => !child.containsKey('properties')), isTrue);
+ expect(children.every((Map<String, Object?> child) => !child.containsKey('properties')), isTrue);
});
});
}
diff --git a/packages/flutter/test/foundation/diagnostics_test.dart b/packages/flutter/test/foundation/diagnostics_test.dart
index fea0b46..a6cef4d 100644
--- a/packages/flutter/test/foundation/diagnostics_test.dart
+++ b/packages/flutter/test/foundation/diagnostics_test.dart
@@ -47,15 +47,15 @@
/// Encode and decode to JSON to make sure all objects in the JSON for the
/// [DiagnosticsNode] are valid JSON.
-Map<String, Object> simulateJsonSerialization(DiagnosticsNode node) {
- return json.decode(json.encode(node.toJsonMap(const DiagnosticsSerializationDelegate()))) as Map<String, Object>;
+Map<String, Object?> simulateJsonSerialization(DiagnosticsNode node) {
+ return json.decode(json.encode(node.toJsonMap(const DiagnosticsSerializationDelegate()))) as Map<String, Object?>;
}
void validateNodeJsonSerialization(DiagnosticsNode node) {
validateNodeJsonSerializationHelper(simulateJsonSerialization(node), node);
}
-void validateNodeJsonSerializationHelper(Map<String, Object> json, DiagnosticsNode node) {
+void validateNodeJsonSerializationHelper(Map<String, Object?> json, DiagnosticsNode node) {
expect(json['name'], equals(node.name));
expect(json['showSeparator'] ?? true, equals(node.showSeparator));
expect(json['description'], equals(node.toDescription()));
@@ -67,18 +67,18 @@
expect(json['hasChildren'] ?? false, equals(node.getChildren().isNotEmpty));
}
-void validatePropertyJsonSerialization(DiagnosticsProperty<Object> property) {
+void validatePropertyJsonSerialization(DiagnosticsProperty<Object?> property) {
validatePropertyJsonSerializationHelper(simulateJsonSerialization(property), property);
}
void validateStringPropertyJsonSerialization(StringProperty property) {
- final Map<String, Object> json = simulateJsonSerialization(property);
+ final Map<String, Object?> json = simulateJsonSerialization(property);
expect(json['quoted'], equals(property.quoted));
validatePropertyJsonSerializationHelper(json, property);
}
void validateFlagPropertyJsonSerialization(FlagProperty property) {
- final Map<String, Object> json = simulateJsonSerialization(property);
+ final Map<String, Object?> json = simulateJsonSerialization(property);
expect(json['ifTrue'], equals(property.ifTrue));
if (property.ifTrue != null) {
@@ -96,7 +96,7 @@
}
void validateDoublePropertyJsonSerialization(DoubleProperty property) {
- final Map<String, Object> json = simulateJsonSerialization(property);
+ final Map<String, Object?> json = simulateJsonSerialization(property);
if (property.unit != null) {
expect(json['unit'], equals(property.unit));
} else {
@@ -109,7 +109,7 @@
}
void validateObjectFlagPropertyJsonSerialization(ObjectFlagProperty<Object> property) {
- final Map<String, Object> json = simulateJsonSerialization(property);
+ final Map<String, Object?> json = simulateJsonSerialization(property);
if (property.ifPresent != null) {
expect(json['ifPresent'], equals(property.ifPresent));
} else {
@@ -120,7 +120,7 @@
}
void validateIterableFlagsPropertyJsonSerialization(FlagsSummary<Object?> property) {
- final Map<String, Object> json = simulateJsonSerialization(property);
+ final Map<String, Object?> json = simulateJsonSerialization(property);
if (property.value.isNotEmpty) {
expect(json['values'], equals(
property.value.entries
@@ -135,9 +135,9 @@
}
void validateIterablePropertyJsonSerialization(IterableProperty<Object> property) {
- final Map<String, Object> json = simulateJsonSerialization(property);
+ final Map<String, Object?> json = simulateJsonSerialization(property);
if (property.value != null) {
- final List<Object> valuesJson = json['values']! as List<Object>;
+ final List<Object?> valuesJson = json['values']! as List<Object?>;
final List<String> expectedValues = property.value!.map<String>((Object value) => value.toString()).toList();
expect(listEquals(valuesJson, expectedValues), isTrue);
} else {
@@ -147,7 +147,7 @@
validatePropertyJsonSerializationHelper(json, property);
}
-void validatePropertyJsonSerializationHelper(final Map<String, Object> json, DiagnosticsProperty<Object> property) {
+void validatePropertyJsonSerializationHelper(final Map<String, Object?> json, DiagnosticsProperty<Object?> property) {
if (property.defaultValue != kNoDefaultValue) {
expect(json['defaultValue'], equals(property.defaultValue.toString()));
} else {
@@ -1919,7 +1919,7 @@
expect(messageProperty.name, equals('diagnostics'));
expect(messageProperty.value, isNull);
expect(messageProperty.showName, isTrue);
- validatePropertyJsonSerialization(messageProperty as DiagnosticsProperty<Object>);
+ validatePropertyJsonSerialization(messageProperty as DiagnosticsProperty<Object?>);
});
test('error message style wrap test', () {
@@ -2246,7 +2246,7 @@
test('DiagnosticsProperty for basic types has value in json', () {
DiagnosticsProperty<int> intProperty = DiagnosticsProperty<int>('int1', 10);
- Map<String, Object> json = simulateJsonSerialization(intProperty);
+ Map<String, Object?> json = simulateJsonSerialization(intProperty);
expect(json['name'], 'int1');
expect(json['value'], 10);
diff --git a/packages/flutter/test/material/app_test.dart b/packages/flutter/test/material/app_test.dart
index 428af5e..46fbcec 100644
--- a/packages/flutter/test/material/app_test.dart
+++ b/packages/flutter/test/material/app_test.dart
@@ -249,7 +249,7 @@
});
testWidgets('Return value from pop is correct', (WidgetTester tester) async {
- late Future<Object> result;
+ late Future<Object?> result;
await tester.pumpWidget(
MaterialApp(
home: Builder(
@@ -258,7 +258,7 @@
child: ElevatedButton(
child: const Text('X'),
onPressed: () async {
- result = Navigator.of(context)!.pushNamed('/a');
+ result = Navigator.of(context)!.pushNamed<Object?>('/a');
},
),
);
diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart
index b1ea26e..e135d56 100644
--- a/packages/flutter/test/material/date_picker_test.dart
+++ b/packages/flutter/test/material/date_picker_test.dart
@@ -83,7 +83,7 @@
Future<void> prepareDatePicker(
WidgetTester tester,
- Future<void> callback(Future<DateTime> date),
+ Future<void> callback(Future<DateTime?> date),
{ TextDirection textDirection = TextDirection.ltr }
) async {
late BuildContext buttonContext;
@@ -105,7 +105,7 @@
await tester.tap(find.text('Go'));
expect(buttonContext, isNotNull);
- final Future<DateTime> date = showDatePicker(
+ final Future<DateTime?> date = showDatePicker(
context: buttonContext,
initialDate: initialDate,
firstDate: firstDate,
@@ -138,7 +138,7 @@
cancelText = 'nope';
confirmText = 'yep';
helpText = 'help';
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
expect(find.text(cancelText!), findsOneWidget);
expect(find.text(confirmText!), findsOneWidget);
expect(find.text(helpText!), findsOneWidget);
@@ -146,21 +146,21 @@
});
testWidgets('Initial date is the default', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('OK'));
expect(await date, DateTime(2016, DateTime.january, 15));
});
});
testWidgets('Can cancel', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('CANCEL'));
expect(await date, isNull);
});
});
testWidgets('Can toggle to input entry mode', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
expect(find.byType(TextField), findsNothing);
await tester.tap(find.byIcon(Icons.edit));
await tester.pumpAndSettle();
@@ -169,7 +169,7 @@
});
testWidgets('Toggle to input mode keeps selected date', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('12'));
await tester.tap(find.byIcon(Icons.edit));
await tester.pumpAndSettle();
@@ -179,7 +179,7 @@
});
testWidgets('Switching to input mode resets input error state', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
// Enter text input mode and type an invalid date to get error.
await tester.tap(find.byIcon(Icons.edit));
await tester.pumpAndSettle();
@@ -438,7 +438,7 @@
group('Calendar mode', () {
testWidgets('Can select a day', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('12'));
await tester.tap(find.text('OK'));
expect(await date, equals(DateTime(2016, DateTime.january, 12)));
@@ -446,7 +446,7 @@
});
testWidgets('Can select a month', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(previousMonthIcon);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.tap(find.text('25'));
@@ -456,7 +456,7 @@
});
testWidgets('Can select a year', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('January 2016')); // Switch to year mode.
await tester.pump();
await tester.tap(find.text('2018'));
@@ -467,7 +467,7 @@
testWidgets('Selecting date does not change displayed month', (WidgetTester tester) async {
initialDate = DateTime(2020, DateTime.march, 15);
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(nextMonthIcon);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('April 2020'), findsOneWidget);
@@ -480,7 +480,7 @@
});
testWidgets('Changing year does not change selected date', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('January 2016'));
await tester.pump();
await tester.tap(find.text('2018'));
@@ -491,7 +491,7 @@
});
testWidgets('Changing year does not change the month', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(nextMonthIcon);
await tester.pumpAndSettle();
await tester.tap(nextMonthIcon);
@@ -505,7 +505,7 @@
});
testWidgets('Can select a year and then a day', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('January 2016')); // Switch to year mode.
await tester.pump();
await tester.tap(find.text('2017'));
@@ -517,7 +517,7 @@
});
testWidgets('Current year is visible in year picker', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('January 2016')); // Switch to year mode.
await tester.pump();
expect(find.text('2016'), findsOneWidget);
@@ -528,7 +528,7 @@
initialDate = DateTime(2017, DateTime.january, 15);
firstDate = initialDate;
lastDate = initialDate;
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
// Earlier than firstDate. Should be ignored.
await tester.tap(find.text('10'));
// Later than lastDate. Should be ignored.
@@ -543,7 +543,7 @@
initialDate = DateTime(2017, DateTime.january, 15);
firstDate = initialDate;
lastDate = DateTime(2017, DateTime.february, 20);
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(nextMonthIcon);
await tester.pumpAndSettle(const Duration(seconds: 1));
// Shouldn't be possible to keep going into March.
@@ -555,7 +555,7 @@
initialDate = DateTime(2017, DateTime.january, 15);
firstDate = DateTime(2016, DateTime.december, 10);
lastDate = initialDate;
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(previousMonthIcon);
await tester.pumpAndSettle(const Duration(seconds: 1));
// Shouldn't be possible to keep going into November.
@@ -567,7 +567,7 @@
initialDate = DateTime(2018, DateTime.july, 4);
firstDate = DateTime(2018, DateTime.june, 9);
lastDate = DateTime(2018, DateTime.december, 15);
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('July 2018')); // Switch to year mode.
await tester.pumpAndSettle();
await tester.tap(find.text('2016')); // Disabled, doesn't change the year.
@@ -582,7 +582,7 @@
initialDate = DateTime(2018, DateTime.may, 4);
firstDate = DateTime(2016, DateTime.june, 9);
lastDate = DateTime(2019, DateTime.january, 15);
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('May 2018'));
await tester.pumpAndSettle();
await tester.tap(find.text('2016'));
@@ -596,7 +596,7 @@
initialDate = DateTime(2018, DateTime.may, 4);
firstDate = DateTime(2016, DateTime.june, 9);
lastDate = DateTime(2019, DateTime.january, 15);
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('May 2018'));
await tester.pumpAndSettle();
await tester.tap(find.text('2019'));
@@ -611,7 +611,7 @@
firstDate = DateTime(2017, DateTime.january, 10);
lastDate = DateTime(2017, DateTime.january, 20);
selectableDayPredicate = (DateTime day) => day.day.isEven;
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('13')); // Odd, doesn't work.
await tester.tap(find.text('10')); // Even, works.
await tester.tap(find.text('17')); // Odd, doesn't work.
@@ -623,7 +623,7 @@
testWidgets('Can select initial calendar picker mode', (WidgetTester tester) async {
initialDate = DateTime(2014, DateTime.january, 15);
initialCalendarMode = DatePickerMode.year;
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.pump();
// 2018 wouldn't be available if the year picker wasn't showing.
// The initial current year is 2014.
@@ -635,7 +635,7 @@
testWidgets('currentDate is highlighted', (WidgetTester tester) async {
today = DateTime(2016, 1, 2);
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.pump();
const Color todayColor = Color(0xff2196f3); // default primary color
expect(
@@ -649,7 +649,7 @@
testWidgets('Selecting date does not switch picker to year selection', (WidgetTester tester) async {
initialDate = DateTime(2020, DateTime.may, 10);
initialCalendarMode = DatePickerMode.year;
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.pump();
await tester.tap(find.text('2017'));
await tester.pump();
@@ -671,7 +671,7 @@
});
testWidgets('Initial entry mode is used', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
expect(find.byType(TextField), findsOneWidget);
});
});
@@ -682,7 +682,7 @@
fieldHintText = 'hint';
fieldLabelText = 'label';
helpText = 'help';
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
expect(find.text(cancelText!), findsOneWidget);
expect(find.text(confirmText!), findsOneWidget);
expect(find.text(fieldHintText!), findsOneWidget);
@@ -692,14 +692,14 @@
});
testWidgets('Initial date is the default', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('OK'));
expect(await date, DateTime(2016, DateTime.january, 15));
});
});
testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
expect(find.byType(TextField), findsOneWidget);
await tester.tap(find.byIcon(Icons.calendar_today));
await tester.pumpAndSettle();
@@ -708,7 +708,7 @@
});
testWidgets('Toggle to calendar mode keeps selected date', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
final TextField field = textField(tester);
field.controller!.clear();
@@ -721,7 +721,7 @@
});
testWidgets('Entered text returns date', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
final TextField field = textField(tester);
field.controller!.clear();
@@ -733,7 +733,7 @@
testWidgets('Too short entered text shows error', (WidgetTester tester) async {
errorFormatText = 'oops';
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
final TextField field = textField(tester);
field.controller!.clear();
@@ -749,7 +749,7 @@
testWidgets('Bad format entered text shows error', (WidgetTester tester) async {
errorFormatText = 'oops';
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
final TextField field = textField(tester);
field.controller!.clear();
@@ -766,7 +766,7 @@
testWidgets('Invalid entered text shows error', (WidgetTester tester) async {
errorInvalidText = 'oops';
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
final TextField field = textField(tester);
field.controller!.clear();
@@ -912,7 +912,7 @@
});
testWidgets('Selecting date vibrates', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('10'));
await tester.pump(hapticFeedbackInterval);
expect(feedback.hapticCount, 1);
@@ -926,7 +926,7 @@
});
testWidgets('Tapping unselectable date does not vibrate', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('11'));
await tester.pump(hapticFeedbackInterval);
expect(feedback.hapticCount, 0);
@@ -940,7 +940,7 @@
});
testWidgets('Changing modes and year vibrates', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('January 2017'));
await tester.pump(hapticFeedbackInterval);
expect(feedback.hapticCount, 1);
@@ -956,7 +956,7 @@
final SemanticsHandle semantics = tester.ensureSemantics();
addTearDown(semantics.dispose);
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
// Header
expect(tester.getSemantics(find.text('SELECT DATE')), matchesSemantics(
label: 'SELECT DATE\nFri, Jan 15',
@@ -1174,7 +1174,7 @@
addTearDown(semantics.dispose);
initialCalendarMode = DatePickerMode.year;
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
// Header
expect(tester.getSemantics(find.text('SELECT DATE')), matchesSemantics(
label: 'SELECT DATE\nFri, Jan 15',
@@ -1231,7 +1231,7 @@
addTearDown(semantics.dispose);
initialEntryMode = DatePickerEntryMode.input;
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
// Header
expect(tester.getSemantics(find.text('SELECT DATE')), matchesSemantics(
label: 'SELECT DATE\nFri, Jan 15',
@@ -1285,7 +1285,7 @@
group('Keyboard navigation', () {
testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
expect(find.byType(TextField), findsNothing);
// Navigate to the entry toggle button and activate it
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1301,7 +1301,7 @@
});
testWidgets('Can toggle to year mode', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
expect(find.text('2016'), findsNothing);
// Navigate to the year selector and activate it
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1313,7 +1313,7 @@
});
testWidgets('Can navigate next/previous months', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
expect(find.text('January 2016'), findsOneWidget);
// Navigate to the previous month button and activate it twice
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1341,7 +1341,7 @@
});
testWidgets('Can navigate date grid with arrow keys', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
// Navigate to the grid
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1377,7 +1377,7 @@
});
testWidgets('Navigating with arrow keys scrolls months', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
// Navigate to the grid
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1425,7 +1425,7 @@
});
testWidgets('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async {
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
// Navigate to the grid
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -1484,7 +1484,7 @@
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
tester.binding.window.devicePixelRatioTestValue = 1.0;
addTearDown(tester.binding.window.clearDevicePixelRatioTestValue);
- await prepareDatePicker(tester, (Future<DateTime> date) async {
+ await prepareDatePicker(tester, (Future<DateTime?> date) async {
await tester.tap(find.text('OK'));
});
await tester.pumpAndSettle();
diff --git a/packages/flutter/test/material/date_range_picker_test.dart b/packages/flutter/test/material/date_range_picker_test.dart
index 8d1bb8a..a890021 100644
--- a/packages/flutter/test/material/date_range_picker_test.dart
+++ b/packages/flutter/test/material/date_range_picker_test.dart
@@ -52,7 +52,7 @@
Future<void> preparePicker(
WidgetTester tester,
- Future<void> callback(Future<DateTimeRange> date),
+ Future<void> callback(Future<DateTimeRange?> date),
{ TextDirection textDirection = TextDirection.ltr }
) async {
late BuildContext buttonContext;
@@ -74,7 +74,7 @@
await tester.tap(find.text('Go'));
expect(buttonContext, isNotNull);
- final Future<DateTimeRange> range = showDateRangePicker(
+ final Future<DateTimeRange?> range = showDateRangePicker(
context: buttonContext,
initialDateRange: initialDateRange,
firstDate: firstDate,
@@ -107,14 +107,14 @@
testWidgets('Save and help text is used', (WidgetTester tester) async {
helpText = 'help';
saveText = 'make it so';
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.text(helpText!), findsOneWidget);
expect(find.text(saveText!), findsOneWidget);
});
});
testWidgets('Initial date is the default', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('SAVE'));
expect(await range, DateTimeRange(
start: DateTime(2016, DateTime.january, 15),
@@ -131,7 +131,7 @@
start: lastDate,
end: lastDate,
);
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
// December header should be showing, but no November
expect(find.text('December 2016'), findsOneWidget);
expect(find.text('November 2016'), findsNothing);
@@ -146,7 +146,7 @@
start: firstDate,
end: firstDate,
);
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
// January and February headers should be showing, but no March
expect(find.text('January 2015'), findsOneWidget);
expect(find.text('February 2015'), findsOneWidget);
@@ -161,7 +161,7 @@
currentDate = DateTime(2016, DateTime.september, 1);
initialDateRange = null;
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
// September and October headers should be showing, but no August
expect(find.text('September 2016'), findsOneWidget);
expect(find.text('October 2016'), findsOneWidget);
@@ -170,14 +170,14 @@
});
testWidgets('Can cancel', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.byIcon(Icons.close));
expect(await range, isNull);
});
});
testWidgets('Can select a range', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('12').first);
await tester.tap(find.text('14').first);
await tester.tap(find.text('SAVE'));
@@ -189,7 +189,7 @@
});
testWidgets('Tapping earlier date resets selected range', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('12').first);
await tester.tap(find.text('11').first);
await tester.tap(find.text('15').first);
@@ -202,7 +202,7 @@
});
testWidgets('Can select single day range', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('12').first);
await tester.tap(find.text('12').first);
await tester.tap(find.text('SAVE'));
@@ -220,7 +220,7 @@
);
firstDate = DateTime(2017, DateTime.january, 12);
lastDate = DateTime(2017, DateTime.january, 16);
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
// Earlier than firstDate. Should be ignored.
await tester.tap(find.text('10'));
// Later than lastDate. Should be ignored.
@@ -232,7 +232,7 @@
});
testWidgets('Can toggle to input entry mode', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNothing);
await tester.tap(find.byIcon(Icons.edit));
await tester.pumpAndSettle();
@@ -241,7 +241,7 @@
});
testWidgets('Toggle to input mode keeps selected date', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('12').first);
await tester.tap(find.text('14').first);
await tester.tap(find.byIcon(Icons.edit));
@@ -262,7 +262,7 @@
testWidgets('Invalid start date', (WidgetTester tester) async {
// Invalid start date should have neither a start nor end date selected in
// calendar mode
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/27/1918');
await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
await tester.tap(find.byIcon(Icons.calendar_today));
@@ -275,7 +275,7 @@
testWidgets('Invalid end date', (WidgetTester tester) async {
// Invalid end date should only have a start date selected
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/24/2016');
await tester.enterText(find.byType(TextField).at(1), '12/25/2050');
await tester.tap(find.byIcon(Icons.calendar_today));
@@ -288,7 +288,7 @@
testWidgets('Invalid range', (WidgetTester tester) async {
// Start date after end date should just use the start date
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
await tester.enterText(find.byType(TextField).at(1), '12/24/2016');
await tester.tap(find.byIcon(Icons.calendar_today));
@@ -375,7 +375,7 @@
});
testWidgets('Selecting dates vibrates', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('10').first);
await tester.pump(hapticFeedbackInterval);
expect(feedback.hapticCount, 1);
@@ -389,7 +389,7 @@
});
testWidgets('Tapping unselectable date does not vibrate', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('8').first);
await tester.pump(hapticFeedbackInterval);
expect(feedback.hapticCount, 0);
@@ -399,7 +399,7 @@
group('Keyboard navigation', () {
testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNothing);
// Navigate to the entry toggle button and activate it
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -412,7 +412,7 @@
});
testWidgets('Can navigate date grid with arrow keys', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
// Navigate to the grid
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -464,7 +464,7 @@
});
testWidgets('Navigating with arrow keys scrolls as needed', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
// Jan and Feb headers should be showing, but no March
expect(find.text('January 2016'), findsOneWidget);
expect(find.text('February 2016'), findsOneWidget);
@@ -529,7 +529,7 @@
});
testWidgets('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
// Navigate to the grid
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
@@ -589,7 +589,7 @@
});
testWidgets('Initial entry mode is used', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNWidgets(2));
});
});
@@ -603,7 +603,7 @@
fieldStartLabelText = 'label1';
fieldEndLabelText = 'label2';
helpText = 'help';
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.text(cancelText!), findsOneWidget);
expect(find.text(confirmText!), findsOneWidget);
expect(find.text(fieldStartHintText!), findsOneWidget);
@@ -615,7 +615,7 @@
});
testWidgets('Initial date is the default', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.tap(find.text('OK'));
expect(await range, DateTimeRange(
start: DateTime(2017, DateTime.january, 15),
@@ -625,7 +625,7 @@
});
testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
expect(find.byType(TextField), findsNWidgets(2));
await tester.tap(find.byIcon(Icons.calendar_today));
await tester.pumpAndSettle();
@@ -635,7 +635,7 @@
testWidgets('Toggle to calendar mode keeps selected date', (WidgetTester tester) async {
initialDateRange = null;
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
await tester.enterText(find.byType(TextField).at(1), '12/27/2016');
await tester.tap(find.byIcon(Icons.calendar_today));
@@ -651,7 +651,7 @@
testWidgets('Entered text returns range', (WidgetTester tester) async {
initialDateRange = null;
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
await tester.enterText(find.byType(TextField).at(1), '12/27/2016');
await tester.tap(find.text('OK'));
@@ -666,7 +666,7 @@
testWidgets('Too short entered text shows error', (WidgetTester tester) async {
initialDateRange = null;
errorFormatText = 'oops';
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/25');
await tester.enterText(find.byType(TextField).at(1), '12/25');
expect(find.text(errorFormatText!), findsNothing);
@@ -680,7 +680,7 @@
testWidgets('Bad format entered text shows error', (WidgetTester tester) async {
initialDateRange = null;
errorFormatText = 'oops';
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '20202014');
await tester.enterText(find.byType(TextField).at(1), '20212014');
expect(find.text(errorFormatText!), findsNothing);
@@ -694,7 +694,7 @@
testWidgets('Invalid entered text shows error', (WidgetTester tester) async {
initialDateRange = null;
errorInvalidText = 'oops';
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '08/08/2014');
await tester.enterText(find.byType(TextField).at(1), '08/08/2014');
expect(find.text(errorInvalidText!), findsNothing);
@@ -708,7 +708,7 @@
testWidgets('End before start date shows error', (WidgetTester tester) async {
initialDateRange = null;
errorInvalidRangeText = 'oops';
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
expect(find.text(errorInvalidRangeText!), findsNothing);
@@ -722,7 +722,7 @@
testWidgets('Error text only displayed for invalid date', (WidgetTester tester) async {
initialDateRange = null;
errorInvalidText = 'oops';
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
await tester.enterText(find.byType(TextField).at(1), '01/01/2018');
expect(find.text(errorInvalidText!), findsNothing);
@@ -735,7 +735,7 @@
testWidgets('End before start date does not get passed to calendar mode', (WidgetTester tester) async {
initialDateRange = null;
- await preparePicker(tester, (Future<DateTimeRange> range) async {
+ await preparePicker(tester, (Future<DateTimeRange?> range) async {
await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart
index cc54bbd..212c499 100644
--- a/packages/flutter/test/material/dialog_test.dart
+++ b/packages/flutter/test/material/dialog_test.dart
@@ -233,7 +233,7 @@
final BuildContext context = tester.element(find.text('Go'));
- final Future<int> result = showDialog<int>(
+ final Future<int?> result = showDialog<int>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
@@ -273,7 +273,7 @@
),
);
- final Future<int> result = showDialog<int>(
+ final Future<int?> result = showDialog<int>(
context: navigator.currentContext!,
builder: (BuildContext context) {
return SimpleDialog(
@@ -1382,7 +1382,7 @@
final List<int> dismissedItems = <int>[];
// Dismiss is confirmed IFF confirmDismiss() returns true.
- Future<bool> confirmDismiss (DismissDirection dismissDirection) {
+ Future<bool?> confirmDismiss (DismissDirection dismissDirection) async {
return showDialog<bool>(
context: _scaffoldKey.currentContext!,
barrierDismissible: true, // showDialog() returns null if tapped outside the dialog
@@ -1762,7 +1762,7 @@
final BuildContext context = tester.element(find.text('Go'));
const RouteSettings exampleSetting = RouteSettings(name: 'simple');
- final Future<int> result = showDialog<int>(
+ final Future<int?> result = showDialog<int>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart
index 35a8646..056e8b6 100644
--- a/packages/flutter/test/material/input_decorator_test.dart
+++ b/packages/flutter/test/material/input_decorator_test.dart
@@ -4253,40 +4253,6 @@
expect(getOpacity(tester, prefixText), 1.0);
});
- testWidgets('OutlineInputBorder with InputDecorator long label, width should ignore icon width', (WidgetTester tester) async {
- // Related issue: https://github.com/flutter/flutter/issues/64427
- const String labelText = 'Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.';
-
- Widget getLabeledInputDecorator(bool useOutlineBorder) => MaterialApp(
- home: Material(
- child: Container(
- width: 300,
- child: TextField(
- decoration: InputDecoration(
- border: useOutlineBorder ? const OutlineInputBorder(
- borderSide: BorderSide(color: Colors.greenAccent, width: 1.0),
- ) : null,
- suffixIcon: const Icon(Icons.arrow_drop_down),
- floatingLabelBehavior: FloatingLabelBehavior.always,
- labelText: labelText,
- ),
- ),
- ),
- ),
- );
-
- // Build with no OutlineInputBorder.
- await tester.pumpWidget(getLabeledInputDecorator(false));
-
- // Get the width of the label when there is no OutlineInputBorder.
- final double labelWidth = tester.getSize(find.text(labelText)).width;
-
- // Build with a OutlineInputBorder.
- await tester.pumpWidget(getLabeledInputDecorator(true));
-
- expect(tester.getSize(find.text(labelText)).width > labelWidth, isTrue);
- });
-
testWidgets('given enough space, constrained and unconstrained heights result in the same size widget', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/65572
final UniqueKey keyUnconstrained = UniqueKey();
diff --git a/packages/flutter/test/material/popup_menu_test.dart b/packages/flutter/test/material/popup_menu_test.dart
index 5347310..d4f5be8 100644
--- a/packages/flutter/test/material/popup_menu_test.dart
+++ b/packages/flutter/test/material/popup_menu_test.dart
@@ -465,7 +465,7 @@
final WidgetPredicate popupMenu = (Widget widget) {
final String widgetType = widget.runtimeType.toString();
// TODO(mraleph): Remove the old case below.
- return widgetType == '_PopupMenu<int>' // normal case
+ return widgetType == '_PopupMenu<int?>' // normal case
|| widgetType == '_PopupMenu'; // for old versions of Dart that don't reify method type arguments
};
@@ -794,7 +794,7 @@
await tester.pumpAndSettle();
// The position is different than the offset because the default position isn't at the origin.
- expect(tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_PopupMenu<int>')), const Offset(364.0, 324.0));
+ expect(tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_PopupMenu<int?>')), const Offset(364.0, 324.0));
});
testWidgets('open PopupMenu has correct semantics', (WidgetTester tester) async {
diff --git a/packages/flutter/test/material/range_slider_test.dart b/packages/flutter/test/material/range_slider_test.dart
index ecf5d68..66c4e71 100644
--- a/packages/flutter/test/material/range_slider_test.dart
+++ b/packages/flutter/test/material/range_slider_test.dart
@@ -1775,6 +1775,7 @@
children: <Matcher>[
matchesSemantics(
isEnabled: true,
+ isSlider: true,
hasEnabledState: true,
hasIncreaseAction: true,
hasDecreaseAction: true,
@@ -1784,6 +1785,7 @@
),
matchesSemantics(
isEnabled: true,
+ isSlider: true,
hasEnabledState: true,
hasIncreaseAction: true,
hasDecreaseAction: true,
diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart
index fd73035..9345317 100644
--- a/packages/flutter/test/material/search_test.dart
+++ b/packages/flutter/test/material/search_test.dart
@@ -80,7 +80,7 @@
// regression test for https://github.com/flutter/flutter/issues/18145
final _TestSearchDelegate delegate = _TestSearchDelegate();
- final List<String> selectedResults = <String>[];
+ final List<String?> selectedResults = <String?>[];
await tester.pumpWidget(TestHomePage(
delegate: delegate,
@@ -105,7 +105,7 @@
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
await tester.pumpAndSettle();
- expect(selectedResults, <void>[null]);
+ expect(selectedResults, <String?>[null]);
// We are on the homepage again
expect(find.text('HomeBody'), findsOneWidget);
@@ -374,7 +374,7 @@
});
testWidgets('Closing nested search returns to search', (WidgetTester tester) async {
- final List<String> nestedSearchResults = <String>[];
+ final List<String?> nestedSearchResults = <String?>[];
final _TestSearchDelegate nestedSearchDelegate = _TestSearchDelegate(
suggestions: 'Nested Suggestions',
result: 'Nested Result',
@@ -389,7 +389,7 @@
tooltip: 'Nested Search',
icon: const Icon(Icons.search),
onPressed: () async {
- final String result = await showSearch(
+ final String? result = await showSearch(
context: context,
delegate: nestedSearchDelegate,
);
@@ -467,7 +467,7 @@
tooltip: 'Nested Search',
icon: const Icon(Icons.search),
onPressed: () async {
- final String result = await showSearch(
+ final String? result = await showSearch(
context: context,
delegate: nestedSearchDelegate,
);
@@ -702,7 +702,7 @@
this.initialQuery,
}) : super(key: key);
- final List<String>? results;
+ final List<String?>? results;
final SearchDelegate<String> delegate;
final bool passInInitialQuery;
final String? initialQuery;
@@ -719,7 +719,7 @@
tooltip: 'Search',
icon: const Icon(Icons.search),
onPressed: () async {
- String selectedResult;
+ String? selectedResult;
if (passInInitialQuery) {
selectedResult = await showSearch<String>(
context: context,
diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart
index 1e8c586..e754216 100644
--- a/packages/flutter/test/material/slider_test.dart
+++ b/packages/flutter/test/material/slider_test.dart
@@ -1345,7 +1345,7 @@
children: <TestSemantics>[
TestSemantics(
id: 4,
- flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
+ flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
value: '50%',
increasedValue: '55%',
@@ -1403,6 +1403,7 @@
SemanticsFlag.hasEnabledState,
// isFocusable is delayed by 1 frame.
SemanticsFlag.isFocusable,
+ SemanticsFlag.isSlider,
],
value: '50%',
increasedValue: '55%',
@@ -1443,6 +1444,7 @@
id: 4,
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
+ SemanticsFlag.isSlider,
],
value: '50%',
increasedValue: '55%',
@@ -1508,7 +1510,7 @@
children: <TestSemantics>[
TestSemantics(
id: 4,
- flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
+ flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
value: '50%',
increasedValue: '60%',
@@ -1562,7 +1564,7 @@
children: <TestSemantics>[
TestSemantics(
id: 5,
- flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState],
+ flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isSlider],
value: '50%',
increasedValue: '60%',
decreasedValue: '40%',
@@ -1623,7 +1625,7 @@
children: <TestSemantics>[
TestSemantics(
id: 4,
- flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
+ flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider],
actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
value: '40',
increasedValue: '60',
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index dd94976..4f4ab2d 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -4331,7 +4331,7 @@
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
await tester.pumpAndSettle();
- const String expected = 'a big a bighouse\njumped over a mouse';
+ const String expected = 'a biga big house\njumped over a mouse';
expect(find.text(expected), findsOneWidget, reason: 'Because text contains ${controller.text}');
});
diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart
index 1296104..cc13240 100644
--- a/packages/flutter/test/material/time_picker_test.dart
+++ b/packages/flutter/test/material/time_picker_test.dart
@@ -23,7 +23,7 @@
this.entryMode = TimePickerEntryMode.dial,
}) : super(key: key);
- final ValueChanged<TimeOfDay> onChanged;
+ final ValueChanged<TimeOfDay?> onChanged;
final Locale? locale;
final TimePickerEntryMode entryMode;
@@ -55,7 +55,7 @@
Future<Offset?> startPicker(
WidgetTester tester,
- ValueChanged<TimeOfDay> onChanged, {
+ ValueChanged<TimeOfDay?> onChanged, {
TimePickerEntryMode entryMode = TimePickerEntryMode.dial,
}) async {
await tester.pumpWidget(_TimePickerLauncher(onChanged: onChanged, locale: const Locale('en', 'US'), entryMode: entryMode));
@@ -82,24 +82,24 @@
void _tests() {
testWidgets('tap-select an hour', (WidgetTester tester) async {
- late TimeOfDay result;
+ TimeOfDay? result;
- Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time; }))!;
await tester.tapAt(Offset(center.dx, center.dy - 50.0)); // 12:00 AM
await finishPicker(tester);
expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
- center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ center = (await startPicker(tester, (TimeOfDay? time) { result = time; }))!;
await tester.tapAt(Offset(center.dx + 50.0, center.dy));
await finishPicker(tester);
expect(result, equals(const TimeOfDay(hour: 3, minute: 0)));
- center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ center = (await startPicker(tester, (TimeOfDay? time) { result = time; }))!;
await tester.tapAt(Offset(center.dx, center.dy + 50.0));
await finishPicker(tester);
expect(result, equals(const TimeOfDay(hour: 6, minute: 0)));
- center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ center = (await startPicker(tester, (TimeOfDay? time) { result = time; }))!;
await tester.tapAt(Offset(center.dx, center.dy + 50.0));
await tester.tapAt(Offset(center.dx - 50, center.dy));
await finishPicker(tester);
@@ -109,7 +109,7 @@
testWidgets('drag-select an hour', (WidgetTester tester) async {
late TimeOfDay result;
- final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
final Offset hour0 = Offset(center.dx, center.dy - 50.0); // 12:00 AM
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
final Offset hour6 = Offset(center.dx, center.dy + 50.0);
@@ -123,21 +123,21 @@
await finishPicker(tester);
expect(result.hour, 0);
- expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
+ expect(await startPicker(tester, (TimeOfDay? time) { result = time!; }), equals(center));
gesture = await tester.startGesture(hour0);
await gesture.moveBy(hour3 - hour0);
await gesture.up();
await finishPicker(tester);
expect(result.hour, 3);
- expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
+ expect(await startPicker(tester, (TimeOfDay? time) { result = time!; }), equals(center));
gesture = await tester.startGesture(hour3);
await gesture.moveBy(hour6 - hour3);
await gesture.up();
await finishPicker(tester);
expect(result.hour, equals(6));
- expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
+ expect(await startPicker(tester, (TimeOfDay? time) { result = time!; }), equals(center));
gesture = await tester.startGesture(hour6);
await gesture.moveBy(hour9 - hour6);
await gesture.up();
@@ -148,7 +148,7 @@
testWidgets('tap-select switches from hour to minute', (WidgetTester tester) async {
late TimeOfDay result;
- final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
final Offset min45 = Offset(center.dx - 50.0, center.dy); // 45 mins (or 9:00 hours)
@@ -162,7 +162,7 @@
testWidgets('drag-select switches from hour to minute', (WidgetTester tester) async {
late TimeOfDay result;
- final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
final Offset hour6 = Offset(center.dx, center.dy + 50.0);
final Offset hour9 = Offset(center.dx - 50.0, center.dy);
@@ -181,7 +181,7 @@
testWidgets('tap-select rounds down to nearest 5 minute increment', (WidgetTester tester) async {
late TimeOfDay result;
- final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
final Offset min46 = Offset(center.dx - 50.0, center.dy - 5); // 46 mins
@@ -195,7 +195,7 @@
testWidgets('tap-select rounds up to nearest 5 minute increment', (WidgetTester tester) async {
late TimeOfDay result;
- final Offset center = (await startPicker(tester, (TimeOfDay time) { result = time; }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { result = time!; }))!;
final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
final Offset min48 = Offset(center.dx - 50.0, center.dy - 15); // 48 mins
@@ -220,14 +220,14 @@
});
testWidgets('tap-select vibrates once', (WidgetTester tester) async {
- final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
await finishPicker(tester);
expect(feedback.hapticCount, 1);
});
testWidgets('quick successive tap-selects vibrate once', (WidgetTester tester) async {
- final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
await tester.pump(kFastFeedbackInterval);
await tester.tapAt(Offset(center.dx, center.dy + 50.0));
@@ -236,7 +236,7 @@
});
testWidgets('slow successive tap-selects vibrate once per tap', (WidgetTester tester) async {
- final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
await tester.pump(kSlowFeedbackInterval);
await tester.tapAt(Offset(center.dx, center.dy + 50.0));
@@ -247,7 +247,7 @@
});
testWidgets('drag-select vibrates once', (WidgetTester tester) async {
- final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
final Offset hour0 = Offset(center.dx, center.dy - 50.0);
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
@@ -259,7 +259,7 @@
});
testWidgets('quick drag-select vibrates once', (WidgetTester tester) async {
- final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
final Offset hour0 = Offset(center.dx, center.dy - 50.0);
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
@@ -275,7 +275,7 @@
});
testWidgets('slow drag-select vibrates once', (WidgetTester tester) async {
- final Offset center = (await startPicker(tester, (TimeOfDay time) { }))!;
+ final Offset center = (await startPicker(tester, (TimeOfDay? time) { }))!;
final Offset hour0 = Offset(center.dx, center.dy - 50.0);
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
@@ -794,7 +794,7 @@
testWidgets('Initial time is the default', (WidgetTester tester) async {
late TimeOfDay result;
- await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
+ await startPicker(tester, (TimeOfDay? time) { result = time!; }, entryMode: TimePickerEntryMode.input);
await finishPicker(tester);
expect(result, equals(const TimeOfDay(hour: 7, minute: 0)));
});
@@ -898,7 +898,7 @@
testWidgets('Entered text returns time', (WidgetTester tester) async {
late TimeOfDay result;
- await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
+ await startPicker(tester, (TimeOfDay? time) { result = time!; }, entryMode: TimePickerEntryMode.input);
await tester.enterText(find.byType(TextField).first, '9');
await tester.enterText(find.byType(TextField).last, '12');
await finishPicker(tester);
@@ -907,7 +907,7 @@
testWidgets('Toggle to dial mode keeps selected time', (WidgetTester tester) async {
late TimeOfDay result;
- await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
+ await startPicker(tester, (TimeOfDay? time) { result = time!; }, entryMode: TimePickerEntryMode.input);
await tester.enterText(find.byType(TextField).first, '8');
await tester.enterText(find.byType(TextField).last, '15');
await tester.tap(find.byIcon(Icons.access_time));
@@ -917,7 +917,7 @@
testWidgets('Invalid text prevents dismissing', (WidgetTester tester) async {
TimeOfDay? result;
- await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
+ await startPicker(tester, (TimeOfDay? time) { result = time; }, entryMode: TimePickerEntryMode.input);
// Invalid hour.
await tester.enterText(find.byType(TextField).first, '88');
@@ -939,7 +939,7 @@
// Fixes regression that was reverted in https://github.com/flutter/flutter/pull/64094#pullrequestreview-469836378.
testWidgets('Ensure hour/minute fields are top-aligned with the separator', (WidgetTester tester) async {
- await startPicker(tester, (TimeOfDay time) { }, entryMode: TimePickerEntryMode.input);
+ await startPicker(tester, (TimeOfDay? time) { }, entryMode: TimePickerEntryMode.input);
final double hourFieldTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField')).dy;
final double minuteFieldTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField')).dy;
final double separatorTop = tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_StringFragment')).dy;
diff --git a/packages/flutter/test/material/will_pop_test.dart b/packages/flutter/test/material/will_pop_test.dart
index c1c05a1..9ff3c72 100644
--- a/packages/flutter/test/material/will_pop_test.dart
+++ b/packages/flutter/test/material/will_pop_test.dart
@@ -222,8 +222,8 @@
});
testWidgets('Form.willPop callbacks do not accumulate', (WidgetTester tester) async {
- Future<bool> showYesNoAlert(BuildContext context) {
- return showDialog<bool>(
+ Future<bool> showYesNoAlert(BuildContext context) async {
+ return (await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
@@ -239,7 +239,7 @@
],
);
},
- );
+ ))!;
}
Widget buildFrame() {
diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart
index 61f1a01..f0da937 100644
--- a/packages/flutter/test/rendering/editable_test.dart
+++ b/packages/flutter/test/rendering/editable_test.dart
@@ -935,6 +935,68 @@
expect(delegate.textEditingValue.text, '');
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61021
+ test('arrow keys with selection text', () async {
+ final TextSelectionDelegate delegate = FakeEditableTextState();
+ final ViewportOffset viewportOffset = ViewportOffset.zero();
+ late TextSelection currentSelection;
+ final RenderEditable editable = RenderEditable(
+ backgroundCursorColor: Colors.grey,
+ selectionColor: Colors.black,
+ textDirection: TextDirection.ltr,
+ cursorColor: Colors.red,
+ offset: viewportOffset,
+ textSelectionDelegate: delegate,
+ onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
+ currentSelection = selection;
+ },
+ startHandleLayerLink: LayerLink(),
+ endHandleLayerLink: LayerLink(),
+ text: const TextSpan(
+ text: '012345', // Thumbs up
+ style: TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'),
+ ),
+ selection: const TextSelection.collapsed(
+ offset: 0,
+ ),
+ );
+
+ layout(editable);
+ editable.hasFocus = true;
+
+ editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
+ pumpFrame();
+
+ await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
+ await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
+ expect(currentSelection.isCollapsed, true);
+ expect(currentSelection.baseOffset, 4);
+
+ editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
+ pumpFrame();
+
+ await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
+ await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
+ expect(currentSelection.isCollapsed, true);
+ expect(currentSelection.baseOffset, 4);
+
+ editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
+ pumpFrame();
+
+ await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
+ await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
+ expect(currentSelection.isCollapsed, true);
+ expect(currentSelection.baseOffset, 2);
+
+ editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
+ pumpFrame();
+
+ await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
+ await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
+ expect(currentSelection.isCollapsed, true);
+ expect(currentSelection.baseOffset, 2);
+
+ }, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068
+
test('getEndpointsForSelection handles empty characters', () {
final TextSelectionDelegate delegate = FakeEditableTextState();
final RenderEditable editable = RenderEditable(
diff --git a/packages/flutter/test/rendering/mock_canvas.dart b/packages/flutter/test/rendering/mock_canvas.dart
index dceae4b..c5165c7 100644
--- a/packages/flutter/test/rendering/mock_canvas.dart
+++ b/packages/flutter/test/rendering/mock_canvas.dart
@@ -823,7 +823,6 @@
while (predicate.moveNext()) {
predicate.current.match(call);
}
- assert(predicate.current == null);
// We allow painting more than expected.
} on _MismatchedCall catch (data) {
description.writeln(data.message);
diff --git a/packages/flutter/test/services/fake_platform_views.dart b/packages/flutter/test/services/fake_platform_views.dart
index c105787..e91c93d 100644
--- a/packages/flutter/test/services/fake_platform_views.dart
+++ b/packages/flutter/test/services/fake_platform_views.dart
@@ -174,11 +174,11 @@
final Map<dynamic, dynamic> args = call.arguments as Map<dynamic, dynamic>;
final int id = args['id'] as int;
final String viewType = args['viewType'] as String;
- final double width = args['width'] as double;
- final double height = args['height'] as double;
+ final double? width = args['width'] as double?;
+ final double? height = args['height'] as double?;
final int layoutDirection = args['direction'] as int;
- final bool hybrid = args['hybrid'] as bool;
- final Uint8List creationParams = args['params'] as Uint8List;
+ final bool? hybrid = args['hybrid'] as bool?;
+ final Uint8List? creationParams = args['params'] as Uint8List?;
if (_views.containsKey(id))
throw PlatformException(
@@ -197,7 +197,7 @@
}
_views[id] = FakeAndroidPlatformView(id, viewType,
- width != null || height != null ? Size(width, height) : null,
+ width != null && height != null ? Size(width, height) : null,
layoutDirection,
hybrid,
creationParams,
@@ -344,7 +344,7 @@
final Map<dynamic, dynamic> args = call.arguments as Map<dynamic, dynamic>;
final int id = args['id'] as int;
final String viewType = args['viewType'] as String;
- final Uint8List creationParams = args['params'] as Uint8List;
+ final Uint8List? creationParams = args['params'] as Uint8List?;
if (_views.containsKey(id)) {
throw PlatformException(
diff --git a/packages/flutter/test/services/platform_views_test.dart b/packages/flutter/test/services/platform_views_test.dart
index 1db8c6a..d438692 100644
--- a/packages/flutter/test/services/platform_views_test.dart
+++ b/packages/flutter/test/services/platform_views_test.dart
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-
import 'package:flutter/painting.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart' show TestWidgetsFlutterBinding;
diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart
index 87fe0ed..535ba24 100644
--- a/packages/flutter/test/services/raw_keyboard_test.dart
+++ b/packages/flutter/test/services/raw_keyboard_test.dart
@@ -756,7 +756,7 @@
Focus(
focusNode: focusNode,
onKey: (FocusNode node, RawKeyEvent event) {
- return true; // handle all events.
+ return KeyEventResult.handled; // handle all events.
},
child: const SizedBox(),
),
diff --git a/packages/flutter/test/widgets/actions_test.dart b/packages/flutter/test/widgets/actions_test.dart
index 5c239f0..6a07c9a 100644
--- a/packages/flutter/test/widgets/actions_test.dart
+++ b/packages/flutter/test/widgets/actions_test.dart
@@ -264,10 +264,7 @@
);
await tester.pump();
- final ActionDispatcher dispatcher = Actions.of(
- containerKey.currentContext!,
- nullOk: true,
- );
+ final ActionDispatcher dispatcher = Actions.of(containerKey.currentContext!);
expect(dispatcher, equals(testDispatcher));
});
testWidgets('Action can be found with find', (WidgetTester tester) async {
diff --git a/packages/flutter/test/widgets/custom_paint_test.dart b/packages/flutter/test/widgets/custom_paint_test.dart
index 24e2d3a..68013a3 100644
--- a/packages/flutter/test/widgets/custom_paint_test.dart
+++ b/packages/flutter/test/widgets/custom_paint_test.dart
@@ -58,7 +58,7 @@
void main() {
testWidgets('Control test for custom painting', (WidgetTester tester) async {
- final List<String> log = <String>[];
+ final List<String?> log = <String?>[];
await tester.pumpWidget(CustomPaint(
painter: TestCustomPainter(
log: log,
@@ -82,7 +82,7 @@
testWidgets('Throws FlutterError on custom painter incorrect restore/save calls', (
WidgetTester tester) async {
final GlobalKey target = GlobalKey();
- final List<String> log = <String>[];
+ final List<String?> log = <String?>[];
await tester.pumpWidget(CustomPaint(
key: target,
isComplex: true,
@@ -174,7 +174,7 @@
testWidgets('Raster cache hints', (WidgetTester tester) async {
final GlobalKey target = GlobalKey();
- final List<String> log = <String>[];
+ final List<String?> log = <String?>[];
await tester.pumpWidget(CustomPaint(
key: target,
isComplex: true,
diff --git a/packages/flutter/test/widgets/dismissible_test.dart b/packages/flutter/test/widgets/dismissible_test.dart
index 1589ad9..1ac1732 100644
--- a/packages/flutter/test/widgets/dismissible_test.dart
+++ b/packages/flutter/test/widgets/dismissible_test.dart
@@ -18,7 +18,7 @@
Widget buildTest({
double? startToEndThreshold,
TextDirection textDirection = TextDirection.ltr,
- Future<bool> Function(BuildContext context, DismissDirection direction)? confirmDismiss,
+ Future<bool?> Function(BuildContext context, DismissDirection direction)? confirmDismiss,
}) {
return Directionality(
textDirection: textDirection,
@@ -679,7 +679,7 @@
return buildTest(
confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
confirmDismissDirection = dismissDirection;
- return Future<bool>.value(confirmDismissValue);
+ return Future<bool?>.value(confirmDismissValue);
}
);
}
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index 89865be..9c7b2f1 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -3989,7 +3989,6 @@
reason: 'on $platform',
);
- // Move forward one character to reset the selection.
await sendKeys(
tester,
<LogicalKeyboardKey>[
@@ -4002,8 +4001,8 @@
selection,
equals(
const TextSelection(
- baseOffset: 21,
- extentOffset: 21,
+ baseOffset: 20,
+ extentOffset: 20,
affinity: TextAffinity.downstream,
),
),
@@ -4024,8 +4023,8 @@
selection,
equals(
const TextSelection(
- baseOffset: 21,
- extentOffset: 40,
+ baseOffset: 20,
+ extentOffset: 39,
affinity: TextAffinity.downstream,
),
),
@@ -4049,7 +4048,7 @@
selection,
equals(
const TextSelection(
- baseOffset: 21,
+ baseOffset: 20,
extentOffset: testText.length,
affinity: TextAffinity.downstream,
),
@@ -4071,8 +4070,8 @@
selection,
equals(
const TextSelection(
- baseOffset: 21,
- extentOffset: 58,
+ baseOffset: 20,
+ extentOffset: 57,
affinity: TextAffinity.downstream,
),
),
@@ -4094,7 +4093,7 @@
selection,
equals(
const TextSelection(
- baseOffset: 21,
+ baseOffset: 20,
extentOffset: 72,
affinity: TextAffinity.downstream,
),
@@ -4117,7 +4116,7 @@
selection,
equals(
const TextSelection(
- baseOffset: 21,
+ baseOffset: 20,
extentOffset: 55,
affinity: TextAffinity.downstream,
),
diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart
index 0cc11a0..d24b195 100644
--- a/packages/flutter/test/widgets/focus_manager_test.dart
+++ b/packages/flutter/test/widgets/focus_manager_test.dart
@@ -852,12 +852,12 @@
testWidgets('Key handling bubbles up and terminates when handled.', (WidgetTester tester) async {
final Set<FocusNode> receivedAnEvent = <FocusNode>{};
final Set<FocusNode> shouldHandle = <FocusNode>{};
- bool handleEvent(FocusNode node, RawKeyEvent event) {
+ KeyEventResult handleEvent(FocusNode node, RawKeyEvent event) {
if (shouldHandle.contains(node)) {
receivedAnEvent.add(node);
- return true;
+ return KeyEventResult.handled;
}
- return false;
+ return KeyEventResult.ignored;
}
Future<void> sendEvent() async {
diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart
index b45b963..1046e02 100644
--- a/packages/flutter/test/widgets/image_test.dart
+++ b/packages/flutter/test/widgets/image_test.dart
@@ -1516,7 +1516,7 @@
expect(imageCache!.liveImageCount, 1);
expect(imageCache!.containsKey(provider), false);
- final ImageCacheStatus providerLocation = await provider.obtainCacheStatus(configuration: ImageConfiguration.empty);
+ final ImageCacheStatus providerLocation = (await provider.obtainCacheStatus(configuration: ImageConfiguration.empty))!;
expect(providerLocation, isNotNull);
expect(providerLocation.live, true);
diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart
index d0de31e..a08d14b 100644
--- a/packages/flutter/test/widgets/navigator_test.dart
+++ b/packages/flutter/test/widgets/navigator_test.dart
@@ -957,7 +957,7 @@
});
testWidgets('replaceNamed returned value', (WidgetTester tester) async {
- late Future<String> value;
+ late Future<String?> value;
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }),
@@ -1001,7 +1001,7 @@
expect(find.text('A'), findsNothing);
expect(find.text('B'), findsNothing);
- final String replaceNamedValue = await value; // replaceNamed result was 'B'
+ final String? replaceNamedValue = await value; // replaceNamed result was 'B'
expect(replaceNamedValue, 'B');
});
@@ -1092,7 +1092,7 @@
});
testWidgets('remove a route whose value is awaited', (WidgetTester tester) async {
- late Future<String> pageValue;
+ late Future<String?> pageValue;
final Map<String, WidgetBuilder> pageBuilders = <String, WidgetBuilder>{
'/': (BuildContext context) => OnTapPage(id: '/', onTap: () { pageValue = Navigator.pushNamed(context, '/A'); }),
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context, 'A'); }),
@@ -1113,7 +1113,7 @@
await tester.tap(find.text('/')); // pushNamed('/A'), stack becomes /, /A
await tester.pumpAndSettle();
- pageValue.then((String value) { assert(false); });
+ pageValue.then((String? value) { assert(false); });
final NavigatorState navigator = tester.state<NavigatorState>(find.byType(Navigator));
navigator.removeRoute(routes['/A']!); // stack becomes /, pageValue will not complete
diff --git a/packages/flutter/test/widgets/restorable_property_test.dart b/packages/flutter/test/widgets/restorable_property_test.dart
index fa63962..85a10de 100644
--- a/packages/flutter/test/widgets/restorable_property_test.dart
+++ b/packages/flutter/test/widgets/restorable_property_test.dart
@@ -285,7 +285,7 @@
});
}
-class _TestRestorableValue extends RestorableValue<Object> {
+class _TestRestorableValue extends RestorableValue<Object?> {
@override
Object createDefaultValue() {
return 55;
@@ -300,12 +300,12 @@
}
@override
- Object fromPrimitives(Object data) {
+ Object? fromPrimitives(Object? data) {
return data;
}
@override
- Object toPrimitives() {
+ Object? toPrimitives() {
return value;
}
}
diff --git a/packages/flutter/test/widgets/restoration_mixin_test.dart b/packages/flutter/test/widgets/restoration_mixin_test.dart
index 8e0ff5d..f44348d 100644
--- a/packages/flutter/test/widgets/restoration_mixin_test.dart
+++ b/packages/flutter/test/widgets/restoration_mixin_test.dart
@@ -632,7 +632,7 @@
});
test('RestorableProperty throws after disposed', () {
- final RestorableProperty<Object> property = _TestRestorableProperty(10);
+ final RestorableProperty<Object?> property = _TestRestorableProperty(10);
property.dispose();
expect(() => property.dispose(), throwsFlutterError);
});
@@ -660,8 +660,8 @@
_TestRestorableProperty? additionalProperty;
bool _rerigisterAdditionalProperty = false;
- final List<RestorationBucket?> restoreStateLog = <RestorationBucket>[];
- final List<RestorationBucket?> toggleBucketLog = <RestorationBucket>[];
+ final List<RestorationBucket?> restoreStateLog = <RestorationBucket?>[];
+ final List<RestorationBucket?> toggleBucketLog = <RestorationBucket?>[];
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
@@ -737,7 +737,7 @@
};
}
-class _TestRestorableProperty extends RestorableProperty<Object> {
+class _TestRestorableProperty extends RestorableProperty<Object?> {
_TestRestorableProperty(this._value);
List<String> log = <String>[];
@@ -751,35 +751,35 @@
}
@override
- Object createDefaultValue() {
+ Object? createDefaultValue() {
log.add('createDefaultValue');
return _value;
}
@override
- Object fromPrimitives(Object data) {
+ Object? fromPrimitives(Object? data) {
log.add('fromPrimitives');
return data;
}
- Object get value {
+ Object? get value {
assert(isRegistered);
return _value;
}
- Object _value;
- set value(Object value) {
+ Object? _value;
+ set value(Object? value) {
_value = value;
notifyListeners();
}
@override
- void initWithValue(Object v) {
+ void initWithValue(Object? v) {
log.add('initWithValue');
_value = v;
}
@override
- Object toPrimitives() {
+ Object? toPrimitives() {
log.add('toPrimitives');
return _value;
}
diff --git a/packages/flutter/test/widgets/restoration_scopes_moving_test.dart b/packages/flutter/test/widgets/restoration_scopes_moving_test.dart
index 3c07df3..cac9033 100644
--- a/packages/flutter/test/widgets/restoration_scopes_moving_test.dart
+++ b/packages/flutter/test/widgets/restoration_scopes_moving_test.dart
@@ -209,7 +209,7 @@
}
class TestWidgetState extends State<TestWidget> with RestorationMixin {
- List<RestorationBucket?> buckets = <RestorationBucket>[];
+ List<RestorationBucket?> buckets = <RestorationBucket?>[];
List<bool> flags = <bool>[];
@override
diff --git a/packages/flutter/test/widgets/root_restoration_scope_test.dart b/packages/flutter/test/widgets/root_restoration_scope_test.dart
index 85d57ce..4a09020 100644
--- a/packages/flutter/test/widgets/root_restoration_scope_test.dart
+++ b/packages/flutter/test/widgets/root_restoration_scope_test.dart
@@ -302,7 +302,7 @@
});
testWidgets('injects null when rootBucket is null', (WidgetTester tester) async {
- final Completer<RestorationBucket> completer = Completer<RestorationBucket>();
+ final Completer<RestorationBucket?> completer = Completer<RestorationBucket?>();
binding.restorationManager.rootBucket = completer.future;
await tester.pumpWidget(
diff --git a/packages/flutter/test/widgets/routes_test.dart b/packages/flutter/test/widgets/routes_test.dart
index 2fd08a3..b5fdfb8 100644
--- a/packages/flutter/test/widgets/routes_test.dart
+++ b/packages/flutter/test/widgets/routes_test.dart
@@ -1319,28 +1319,28 @@
final Finder animatedModalBarrier = find.byType(AnimatedModalBarrier);
expect(animatedModalBarrier, findsOneWidget);
- Animation<Color> modalBarrierAnimation;
+ Animation<Color?> modalBarrierAnimation;
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(modalBarrierAnimation.value, Colors.transparent);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
- modalBarrierAnimation.value.alpha,
+ modalBarrierAnimation.value!.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1),
);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
- modalBarrierAnimation.value.alpha,
+ modalBarrierAnimation.value!.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1),
);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
- modalBarrierAnimation.value.alpha,
+ modalBarrierAnimation.value!.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1),
);
@@ -1382,28 +1382,28 @@
final Finder animatedModalBarrier = find.byType(AnimatedModalBarrier);
expect(animatedModalBarrier, findsOneWidget);
- Animation<Color> modalBarrierAnimation;
+ Animation<Color?> modalBarrierAnimation;
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(modalBarrierAnimation.value, Colors.transparent);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
- modalBarrierAnimation.value.alpha,
+ modalBarrierAnimation.value!.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1),
);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
- modalBarrierAnimation.value.alpha,
+ modalBarrierAnimation.value!.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1),
);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
- modalBarrierAnimation.value.alpha,
+ modalBarrierAnimation.value!.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1),
);
diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart
index 05cc15e..595dcda 100644
--- a/packages/flutter/test/widgets/shortcuts_test.dart
+++ b/packages/flutter/test/widgets/shortcuts_test.dart
@@ -28,7 +28,7 @@
@override
Object? invokeAction(Action<TestIntent> action, Intent intent, [BuildContext? context]) {
final Object? result = super.invokeAction(action, intent, context);
- postInvoke?.call(action: action, intent: intent, context: context, dispatcher: this);
+ postInvoke?.call(action: action, intent: intent, context: context!, dispatcher: this);
return result;
}
}
@@ -43,8 +43,10 @@
List<LogicalKeyboardKey> keys;
@override
- bool handleKeypress(BuildContext context, RawKeyEvent event, {LogicalKeySet? keysPressed}) {
- keys.add(event.logicalKey);
+ KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event, {LogicalKeySet? keysPressed}) {
+ if (event is RawKeyDownEvent) {
+ keys.add(event.logicalKey);
+ }
return super.handleKeypress(context, event, keysPressed: keysPressed);
}
}
@@ -320,6 +322,142 @@
expect(invoked, isFalse);
expect(pressedKeys, isEmpty);
});
+ testWidgets("Shortcuts that aren't bound to an action don't absorb keys meant for text fields", (WidgetTester tester) async {
+ final GlobalKey textFieldKey = GlobalKey();
+ final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
+ final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Material(
+ child: Shortcuts(
+ manager: testManager,
+ shortcuts: <LogicalKeySet, Intent>{
+ LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
+ },
+ child: TextField(key: textFieldKey, autofocus: true),
+ ),
+ ),
+ ),
+ );
+ await tester.pump();
+ expect(Shortcuts.of(textFieldKey.currentContext!), isNotNull);
+ final bool handled = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
+ expect(handled, isFalse);
+ expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.keyA]));
+ });
+ testWidgets('Shortcuts that are bound to an action do override text fields', (WidgetTester tester) async {
+ final GlobalKey textFieldKey = GlobalKey();
+ final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
+ final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
+ bool invoked = false;
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Material(
+ child: Shortcuts(
+ manager: testManager,
+ shortcuts: <LogicalKeySet, Intent>{
+ LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
+ },
+ child: Actions(
+ actions: <Type, Action<Intent>>{
+ TestIntent: TestAction(
+ onInvoke: (Intent intent) {
+ invoked = true;
+ return invoked;
+ },
+ ),
+ },
+ child: TextField(key: textFieldKey, autofocus: true),
+ ),
+ ),
+ ),
+ ),
+ );
+ await tester.pump();
+ expect(Shortcuts.of(textFieldKey.currentContext!), isNotNull);
+ final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
+ expect(result, isTrue);
+ expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.keyA]));
+ expect(invoked, isTrue);
+ });
+ testWidgets('Shortcuts can override intents that apply to text fields', (WidgetTester tester) async {
+ final GlobalKey textFieldKey = GlobalKey();
+ final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
+ final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
+ bool invoked = false;
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Material(
+ child: Shortcuts(
+ manager: testManager,
+ shortcuts: <LogicalKeySet, Intent>{
+ LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
+ },
+ child: Actions(
+ actions: <Type, Action<Intent>>{
+ TestIntent: TestAction(
+ onInvoke: (Intent intent) {
+ invoked = true;
+ return invoked;
+ },
+ ),
+ },
+ child: Actions(
+ actions: <Type, Action<Intent>>{
+ TestIntent: DoNothingAction(consumesKey: false),
+ },
+ child: TextField(key: textFieldKey, autofocus: true),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ await tester.pump();
+ expect(Shortcuts.of(textFieldKey.currentContext!), isNotNull);
+ final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
+ expect(result, isFalse);
+ expect(invoked, isFalse);
+ });
+ testWidgets('Shortcuts can override intents that apply to text fields with DoNothingAndStopPropagationIntent', (WidgetTester tester) async {
+ final GlobalKey textFieldKey = GlobalKey();
+ final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
+ final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
+ bool invoked = false;
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Material(
+ child: Shortcuts(
+ manager: testManager,
+ shortcuts: <LogicalKeySet, Intent>{
+ LogicalKeySet(LogicalKeyboardKey.keyA): const TestIntent(),
+ },
+ child: Actions(
+ actions: <Type, Action<Intent>>{
+ TestIntent: TestAction(
+ onInvoke: (Intent intent) {
+ invoked = true;
+ return invoked;
+ },
+ ),
+ },
+ child: Shortcuts(
+ shortcuts: <LogicalKeySet, Intent>{
+ LogicalKeySet(LogicalKeyboardKey.keyA): DoNothingAndStopPropagationIntent(),
+ },
+ child: TextField(key: textFieldKey, autofocus: true),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ await tester.pump();
+ expect(Shortcuts.of(textFieldKey.currentContext!), isNotNull);
+ final bool result = await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
+ expect(result, isFalse);
+ expect(invoked, isFalse);
+ });
test('Shortcuts diagnostics work.', () {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart
index 4e4546e..ed6e282 100644
--- a/packages/flutter/test/widgets/widget_inspector_test.dart
+++ b/packages/flutter/test/widgets/widget_inspector_test.dart
@@ -780,7 +780,7 @@
final String bId = service.toId(elementB, group)!;
final Object? jsonList = json.decode(service.getParentChain(bId, group));
expect(jsonList, isList);
- final List<Object> chainElements = jsonList! as List<Object>;
+ final List<Object?> chainElements = jsonList! as List<Object?>;
final List<Element> expectedChain = elementB.debugGetDiagnosticChain().reversed.toList();
// Sanity check that the chain goes back to the root.
expect(expectedChain.first, tester.binding.renderViewElement);
@@ -788,15 +788,15 @@
expect(chainElements.length, equals(expectedChain.length));
for (int i = 0; i < expectedChain.length; i += 1) {
expect(chainElements[i], isMap);
- final Map<String, Object> chainNode = chainElements[i] as Map<String, Object>;
+ final Map<String, Object?> chainNode = chainElements[i]! as Map<String, Object?>;
final Element element = expectedChain[i];
expect(chainNode['node'], isMap);
- final Map<String, Object> jsonNode = chainNode['node']! as Map<String, Object>;
+ final Map<String, Object?> jsonNode = chainNode['node']! as Map<String, Object?>;
expect(service.toObject(jsonNode['valueId']! as String), equals(element));
expect(service.toObject(jsonNode['objectId']! as String), isA<DiagnosticsNode>());
expect(chainNode['children'], isList);
- final List<Object> jsonChildren = chainNode['children']! as List<Object>;
+ final List<Object?> jsonChildren = chainNode['children']! as List<Object?>;
final List<Element> childrenElements = <Element>[];
element.visitChildren(childrenElements.add);
expect(jsonChildren.length, equals(childrenElements.length));
@@ -807,7 +807,7 @@
}
for (int j = 0; j < childrenElements.length; j += 1) {
expect(jsonChildren[j], isMap);
- final Map<String, Object> childJson = jsonChildren[j] as Map<String, Object>;
+ final Map<String, Object?> childJson = jsonChildren[j]! as Map<String, Object?>;
expect(service.toObject(childJson['valueId']! as String), equals(childrenElements[j]));
expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>());
}
@@ -819,12 +819,12 @@
const String group = 'group';
service.disposeAllGroups();
final String id = service.toId(diagnostic, group)!;
- final List<Object> propertiesJson = json.decode(service.getProperties(id, group)) as List<Object>;
+ final List<Object?> propertiesJson = json.decode(service.getProperties(id, group)) as List<Object?>;
final List<DiagnosticsNode> properties = diagnostic.getProperties();
expect(properties, isNotEmpty);
expect(propertiesJson.length, equals(properties.length));
for (int i = 0; i < propertiesJson.length; ++i) {
- final Map<String, Object> propertyJson = propertiesJson[i] as Map<String, Object>;
+ final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>;
expect(service.toObject(propertyJson['valueId'] as String?), equals(properties[i].value));
expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>());
}
@@ -848,12 +848,12 @@
final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode();
service.disposeAllGroups();
final String id = service.toId(diagnostic, group)!;
- final List<Object> propertiesJson = json.decode(service.getChildren(id, group)) as List<Object>;
+ final List<Object?> propertiesJson = json.decode(service.getChildren(id, group)) as List<Object?>;
final List<DiagnosticsNode> children = diagnostic.getChildren();
expect(children.length, equals(3));
expect(propertiesJson.length, equals(children.length));
for (int i = 0; i < propertiesJson.length; ++i) {
- final Map<String, Object> propertyJson = propertiesJson[i] as Map<String, Object>;
+ final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>;
expect(service.toObject(propertyJson['valueId']! as String), equals(children[i].value));
expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>());
}
@@ -878,22 +878,22 @@
service.disposeAllGroups();
service.setPubRootDirectories(<String>[]);
service.setSelection(elementA, 'my-group');
- final Map<String, Object> jsonA = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
- final Map<String, Object> creationLocationA = jsonA['creationLocation']! as Map<String, Object>;
+ final Map<String, Object?> jsonA = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+ final Map<String, Object?> creationLocationA = jsonA['creationLocation']! as Map<String, Object?>;
expect(creationLocationA, isNotNull);
final String fileA = creationLocationA['file']! as String;
final int lineA = creationLocationA['line']! as int;
final int columnA = creationLocationA['column']! as int;
- final List<Object> parameterLocationsA = creationLocationA['parameterLocations']! as List<Object>;
+ final List<Object?> parameterLocationsA = creationLocationA['parameterLocations']! as List<Object?>;
service.setSelection(elementB, 'my-group');
- final Map<String, Object> jsonB = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
- final Map<String, Object> creationLocationB = jsonB['creationLocation']! as Map<String, Object>;
+ final Map<String, Object?> jsonB = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+ final Map<String, Object?> creationLocationB = jsonB['creationLocation']! as Map<String, Object?>;
expect(creationLocationB, isNotNull);
final String fileB = creationLocationB['file']! as String;
final int lineB = creationLocationB['line']! as int;
final int columnB = creationLocationB['column']! as int;
- final List<Object> parameterLocationsB = creationLocationB['parameterLocations']! as List<Object>;
+ final List<Object?> parameterLocationsB = creationLocationB['parameterLocations']! as List<Object?>;
expect(fileA, endsWith('widget_inspector_test.dart'));
expect(fileA, equals(fileB));
// We don't hardcode the actual lines the widgets are created on as that
@@ -903,17 +903,17 @@
expect(columnA, equals(15));
expect(columnA, equals(columnB));
expect(parameterLocationsA.length, equals(1));
- final Map<String, Object> paramA = parameterLocationsA[0] as Map<String, Object>;
+ final Map<String, Object?> paramA = parameterLocationsA[0]! as Map<String, Object?>;
expect(paramA['name'], equals('data'));
expect(paramA['line'], equals(lineA));
expect(paramA['column'], equals(20));
expect(parameterLocationsB.length, equals(2));
- final Map<String, Object> paramB1 = parameterLocationsB[0] as Map<String, Object>;
+ final Map<String, Object?> paramB1 = parameterLocationsB[0]! as Map<String, Object?>;
expect(paramB1['name'], equals('data'));
expect(paramB1['line'], equals(lineB));
expect(paramB1['column'], equals(20));
- final Map<String, Object> paramB2 = parameterLocationsB[1] as Map<String, Object>;
+ final Map<String, Object?> paramB2 = parameterLocationsB[1]! as Map<String, Object?>;
expect(paramB2['name'], equals('textDirection'));
expect(paramB2['line'], equals(lineB));
expect(paramB2['column'], equals(25));
@@ -936,9 +936,9 @@
final Element elementA = find.text('a').evaluate().first;
late String pubRootTest;
if (widgetTracked) {
- final Map<String, Object> jsonObject = json.decode(
- service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
- final Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+ final Map<String, Object?> jsonObject = json.decode(
+ service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+ final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String fileA = creationLocation['file']! as String;
expect(fileA, endsWith('widget_inspector_test.dart'));
@@ -998,9 +998,9 @@
final Element elementA = find.text('a').evaluate().first;
late String pubRootTest;
if (widgetTracked) {
- final Map<String, Object> jsonObject = json.decode(
- service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
- final Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+ final Map<String, Object?> jsonObject = json.decode(
+ service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+ final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String fileA = creationLocation['file']! as String;
expect(fileA, endsWith('widget_inspector_test.dart'));
@@ -1061,8 +1061,8 @@
service.disposeAllGroups();
service.setPubRootDirectories(<String>[]);
service.setSelection(elementA, 'my-group');
- Map<String, Object> jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
- Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+ Map<String, Object?> jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
+ Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String fileA = creationLocation['file']! as String;
expect(fileA, endsWith('widget_inspector_test.dart'));
@@ -1099,9 +1099,9 @@
).evaluate().first;
service.setSelection(richText, 'my-group');
service.setPubRootDirectories(<String>[pubRootTest]);
- jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
+ jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
expect(jsonObject, isNot(contains('createdByLocalProject')));
- creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+ creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
// This RichText widget is created by the build method of the Text widget
// thus the creation location is in text.dart not basic.dart
@@ -1220,7 +1220,7 @@
final String bId = service.toId(elementB, group)!;
final Object? jsonList = await service.testExtension('getParentChain', <String, String>{'arg': bId, 'objectGroup': group});
expect(jsonList, isList);
- final List<Object> chainElements = jsonList! as List<Object>;
+ final List<Object?> chainElements = jsonList! as List<Object?>;
final List<Element> expectedChain = elementB.debugGetDiagnosticChain().reversed.toList();
// Sanity check that the chain goes back to the root.
expect(expectedChain.first, tester.binding.renderViewElement);
@@ -1228,15 +1228,15 @@
expect(chainElements.length, equals(expectedChain.length));
for (int i = 0; i < expectedChain.length; i += 1) {
expect(chainElements[i], isMap);
- final Map<String, Object> chainNode = chainElements[i] as Map<String, Object>;
+ final Map<String, Object?> chainNode = chainElements[i]! as Map<String, Object?>;
final Element element = expectedChain[i];
expect(chainNode['node'], isMap);
- final Map<String, Object> jsonNode = chainNode['node']! as Map<String, Object>;
+ final Map<String, Object?> jsonNode = chainNode['node']! as Map<String, Object?>;
expect(service.toObject(jsonNode['valueId']! as String), equals(element));
expect(service.toObject(jsonNode['objectId']! as String), isA<DiagnosticsNode>());
expect(chainNode['children'], isList);
- final List<Object> jsonChildren = chainNode['children']! as List<Object>;
+ final List<Object?> jsonChildren = chainNode['children']! as List<Object?>;
final List<Element> childrenElements = <Element>[];
element.visitChildren(childrenElements.add);
expect(jsonChildren.length, equals(childrenElements.length));
@@ -1247,7 +1247,7 @@
}
for (int j = 0; j < childrenElements.length; j += 1) {
expect(jsonChildren[j], isMap);
- final Map<String, Object> childJson = jsonChildren[j] as Map<String, Object>;
+ final Map<String, Object?> childJson = jsonChildren[j]! as Map<String, Object?>;
expect(service.toObject(childJson['valueId']! as String), equals(childrenElements[j]));
expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>());
}
@@ -1258,12 +1258,12 @@
final DiagnosticsNode diagnostic = const Text('a', textDirection: TextDirection.ltr).toDiagnosticsNode();
const String group = 'group';
final String id = service.toId(diagnostic, group)!;
- final List<Object> propertiesJson = (await service.testExtension('getProperties', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object>;
+ final List<Object?> propertiesJson = (await service.testExtension('getProperties', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object?>;
final List<DiagnosticsNode> properties = diagnostic.getProperties();
expect(properties, isNotEmpty);
expect(propertiesJson.length, equals(properties.length));
for (int i = 0; i < propertiesJson.length; ++i) {
- final Map<String, Object> propertyJson = propertiesJson[i] as Map<String, Object>;
+ final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>;
expect(service.toObject(propertyJson['valueId'] as String?), equals(properties[i].value));
expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>());
}
@@ -1286,12 +1286,12 @@
);
final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode();
final String id = service.toId(diagnostic, group)!;
- final List<Object> propertiesJson = (await service.testExtension('getChildren', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object>;
+ final List<Object?> propertiesJson = (await service.testExtension('getChildren', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object?>;
final List<DiagnosticsNode> children = diagnostic.getChildren();
expect(children.length, equals(3));
expect(propertiesJson.length, equals(children.length));
for (int i = 0; i < propertiesJson.length; ++i) {
- final Map<String, Object> propertyJson = propertiesJson[i] as Map<String, Object>;
+ final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>;
expect(service.toObject(propertyJson['valueId']! as String), equals(children[i].value));
expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>());
}
@@ -1314,18 +1314,18 @@
);
final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode();
final String id = service.toId(diagnostic, group)!;
- final List<Object> childrenJson = (await service.testExtension('getChildrenDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object>;
+ final List<Object?> childrenJson = (await service.testExtension('getChildrenDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as List<Object?>;
final List<DiagnosticsNode> children = diagnostic.getChildren();
expect(children.length, equals(3));
expect(childrenJson.length, equals(children.length));
for (int i = 0; i < childrenJson.length; ++i) {
- final Map<String, Object> childJson = childrenJson[i] as Map<String, Object>;
+ final Map<String, Object?> childJson = childrenJson[i]! as Map<String, Object?>;
expect(service.toObject(childJson['valueId']! as String), equals(children[i].value));
expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>());
- final List<Object> propertiesJson = childJson['properties']! as List<Object>;
+ final List<Object?> propertiesJson = childJson['properties']! as List<Object?>;
final DiagnosticsNode diagnosticsNode = service.toObject(childJson['objectId']! as String)! as DiagnosticsNode;
final List<DiagnosticsNode> expectedProperties = diagnosticsNode.getProperties();
- for (final Map<String, Object> propertyJson in propertiesJson.cast<Map<String, Object>>()) {
+ for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) {
final Object? property = service.toObject(propertyJson['objectId']! as String);
expect(property, isA<DiagnosticsNode>());
expect(expectedProperties, contains(property));
@@ -1350,37 +1350,37 @@
);
final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode();
final String id = service.toId(diagnostic, group)!;
- final Map<String, Object> subtreeJson = (await service.testExtension('getDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as Map<String, Object>;
+ final Map<String, Object?> subtreeJson = (await service.testExtension('getDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as Map<String, Object?>;
expect(subtreeJson['objectId'], equals(id));
- final List<Object> childrenJson = subtreeJson['children']! as List<Object>;
+ final List<Object?> childrenJson = subtreeJson['children']! as List<Object?>;
final List<DiagnosticsNode> children = diagnostic.getChildren();
expect(children.length, equals(3));
expect(childrenJson.length, equals(children.length));
for (int i = 0; i < childrenJson.length; ++i) {
- final Map<String, Object> childJson = childrenJson[i] as Map<String, Object>;
+ final Map<String, Object?> childJson = childrenJson[i]! as Map<String, Object?>;
expect(service.toObject(childJson['valueId']! as String), equals(children[i].value));
expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>());
- final List<Object> propertiesJson = childJson['properties']! as List<Object>;
- for (final Map<String, Object> propertyJson in propertiesJson.cast<Map<String, Object>>()) {
+ final List<Object?> propertiesJson = childJson['properties']! as List<Object?>;
+ for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) {
expect(propertyJson, isNot(contains('children')));
}
final DiagnosticsNode diagnosticsNode = service.toObject(childJson['objectId']! as String)! as DiagnosticsNode;
final List<DiagnosticsNode> expectedProperties = diagnosticsNode.getProperties();
- for (final Map<String, Object> propertyJson in propertiesJson.cast<Map<String, Object>>()) {
+ for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) {
final Object property = service.toObject(propertyJson['objectId']! as String)!;
expect(property, isA<DiagnosticsNode>());
expect(expectedProperties, contains(property));
}
}
- final Map<String, Object> deepSubtreeJson = (await service.testExtension(
+ final Map<String, Object?> deepSubtreeJson = (await service.testExtension(
'getDetailsSubtree',
<String, String>{'arg': id, 'objectGroup': group, 'subtreeDepth': '3'},
- ))! as Map<String, Object>;
- final List<Object> deepChildrenJson = deepSubtreeJson['children']! as List<Object>;
- for (final Map<String, Object> childJson in deepChildrenJson.cast<Map<String, Object>>()) {
- final List<Object> propertiesJson = childJson['properties']! as List<Object>;
- for (final Map<String, Object> propertyJson in propertiesJson.cast<Map<String, Object>>()) {
+ ))! as Map<String, Object?>;
+ final List<Object?> deepChildrenJson = deepSubtreeJson['children']! as List<Object?>;
+ for (final Map<String, Object?> childJson in deepChildrenJson.cast<Map<String, Object?>>()) {
+ final List<Object?> propertiesJson = childJson['properties']! as List<Object?>;
+ for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) {
expect(propertyJson, contains('children'));
}
}
@@ -1396,20 +1396,20 @@
final DiagnosticsNode diagnostic = a.toDiagnosticsNode();
final String id = service.toId(diagnostic, group)!;
- final Map<String, Object> subtreeJson = (await service.testExtension('getDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as Map<String, Object>;
+ final Map<String, Object?> subtreeJson = (await service.testExtension('getDetailsSubtree', <String, String>{'arg': id, 'objectGroup': group}))! as Map<String, Object?>;
expect(subtreeJson['objectId'], equals(id));
expect(subtreeJson, contains('children'));
- final List<Object> propertiesJson = subtreeJson['properties']! as List<Object>;
+ final List<Object?> propertiesJson = subtreeJson['properties']! as List<Object?>;
expect(propertiesJson.length, equals(1));
- final Map<String, Object> relatedProperty = propertiesJson.first as Map<String, Object>;
+ final Map<String, Object?> relatedProperty = propertiesJson.first! as Map<String, Object?>;
expect(relatedProperty['name'], equals('related'));
expect(relatedProperty['description'], equals('CyclicDiagnostic-b'));
expect(relatedProperty, contains('isDiagnosticableValue'));
expect(relatedProperty, isNot(contains('children')));
expect(relatedProperty, contains('properties'));
- final List<Object> relatedWidgetProperties = relatedProperty['properties']! as List<Object>;
+ final List<Object?> relatedWidgetProperties = relatedProperty['properties']! as List<Object?>;
expect(relatedWidgetProperties.length, equals(1));
- final Map<String, Object> nestedRelatedProperty = relatedWidgetProperties.first as Map<String, Object>;
+ final Map<String, Object?> nestedRelatedProperty = relatedWidgetProperties.first! as Map<String, Object?>;
expect(nestedRelatedProperty['name'], equals('related'));
// Make sure we do not include properties or children for diagnostic a
// which we already included as the root node as that would indicate a
@@ -1440,20 +1440,20 @@
service.disposeAllGroups();
await service.testExtension('setPubRootDirectories', <String, String>{});
service.setSelection(elementA, 'my-group');
- final Map<String, Object?> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+ final Map<String, dynamic?> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, dynamic?>;
await service.testExtension('setPubRootDirectories', <String, String>{});
- Map<String, Object> rootJson = (await service.testExtension('getRootWidgetSummaryTree', <String, String>{'objectGroup': group}))! as Map<String, Object>;
+ Map<String, Object?> rootJson = (await service.testExtension('getRootWidgetSummaryTree', <String, String>{'objectGroup': group}))! as Map<String, Object?>;
// We haven't yet properly specified which directories are summary tree
// directories so we get an empty tree other than the root that is always
// included.
final Object? rootWidget = service.toObject(rootJson['valueId']! as String);
expect(rootWidget, equals(WidgetsBinding.instance?.renderViewElement));
- List<Object> childrenJson = rootJson['children']! as List<Object>;
+ List<Object?> childrenJson = rootJson['children']! as List<Object?>;
// There are no summary tree children.
expect(childrenJson.length, equals(0));
- final Map<String, Object> creationLocation = jsonA['creationLocation']! as Map<String, Object>;
+ final Map<String, Object?> creationLocation = jsonA['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String testFile = creationLocation['file']! as String;
expect(testFile, endsWith('widget_inspector_test.dart'));
@@ -1463,42 +1463,42 @@
final String pubRootTest = '/' + segments.take(segments.length - 2).join('/');
await service.testExtension('setPubRootDirectories', <String, String>{'arg0': pubRootTest});
- rootJson = (await service.testExtension('getRootWidgetSummaryTree', <String, String>{'objectGroup': group}))! as Map<String, Object>;
- childrenJson = rootJson['children']! as List<Object>;
+ rootJson = (await service.testExtension('getRootWidgetSummaryTree', <String, String>{'objectGroup': group}))! as Map<String, Object?>;
+ childrenJson = rootJson['children']! as List<Object?>;
// The tree of nodes returned contains all widgets created directly by the
// test.
- childrenJson = rootJson['children']! as List<Object>;
+ childrenJson = rootJson['children']! as List<Object?>;
expect(childrenJson.length, equals(1));
- List<Object> alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': rootJson['objectId']! as String, 'objectGroup': group}))! as List<Object>;
+ List<Object?> alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': rootJson['objectId']! as String, 'objectGroup': group}))! as List<Object?>;
expect(alternateChildrenJson.length, equals(1));
- Map<String, Object> childJson = childrenJson[0] as Map<String, Object>;
- Map<String, Object> alternateChildJson = alternateChildrenJson[0] as Map<String, Object>;
+ Map<String, Object?> childJson = childrenJson[0]! as Map<String, Object?>;
+ Map<String, Object?> alternateChildJson = alternateChildrenJson[0]! as Map<String, Object?>;
expect(childJson['description'], startsWith('Directionality'));
expect(alternateChildJson['description'], startsWith('Directionality'));
expect(alternateChildJson['valueId'], equals(childJson['valueId']));
- childrenJson = childJson['children']! as List<Object>;
- alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object>;
+ childrenJson = childJson['children']! as List<Object?>;
+ alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object?>;
expect(alternateChildrenJson.length, equals(1));
expect(childrenJson.length, equals(1));
- alternateChildJson = alternateChildrenJson[0] as Map<String, Object>;
- childJson = childrenJson[0] as Map<String, Object>;
+ alternateChildJson = alternateChildrenJson[0]! as Map<String, Object?>;
+ childJson = childrenJson[0]! as Map<String, Object?>;
expect(childJson['description'], startsWith('Stack'));
expect(alternateChildJson['description'], startsWith('Stack'));
expect(alternateChildJson['valueId'], equals(childJson['valueId']));
- childrenJson = childJson['children']! as List<Object>;
+ childrenJson = childJson['children']! as List<Object?>;
- childrenJson = childJson['children']! as List<Object>;
- alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object>;
+ childrenJson = childJson['children']! as List<Object?>;
+ alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object?>;
expect(alternateChildrenJson.length, equals(3));
expect(childrenJson.length, equals(3));
- alternateChildJson = alternateChildrenJson[2] as Map<String, Object>;
- childJson = childrenJson[2] as Map<String, Object>;
+ alternateChildJson = alternateChildrenJson[2]! as Map<String, Object?>;
+ childJson = childrenJson[2]! as Map<String, Object?>;
expect(childJson['description'], startsWith('Text'));
expect(alternateChildJson['description'], startsWith('Text'));
expect(alternateChildJson['valueId'], equals(childJson['valueId']));
- alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object>;
+ alternateChildrenJson = (await service.testExtension('getChildrenSummaryTree', <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}))! as List<Object?>;
expect(alternateChildrenJson.length , equals(0));
expect(childJson['chidlren'], isNull);
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // Test requires --track-widget-creation flag.
@@ -1527,16 +1527,16 @@
service.disposeAllGroups();
await service.testExtension('setPubRootDirectories', <String, String>{});
service.setSelection(elementA, 'my-group');
- final Map<String, Object> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
+ final Map<String, Object?> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
service.setSelection(richTextDiagnostic.value, 'my-group');
await service.testExtension('setPubRootDirectories', <String, String>{});
- Map<String, Object>? summarySelection = await service.testExtension('getSelectedSummaryWidget', <String, String>{'objectGroup': group}) as Map<String, Object>?;
+ Map<String, Object?>? summarySelection = await service.testExtension('getSelectedSummaryWidget', <String, String>{'objectGroup': group}) as Map<String, Object?>?;
// No summary selection because we haven't set the pub root directories
// yet to indicate what directories are in the summary tree.
expect(summarySelection, isNull);
- final Map<String, Object> creationLocation = jsonA['creationLocation']! as Map<String, Object>;
+ final Map<String, Object?> creationLocation = jsonA['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String testFile = creationLocation['file']! as String;
expect(testFile, endsWith('widget_inspector_test.dart'));
@@ -1546,7 +1546,7 @@
final String pubRootTest = '/' + segments.take(segments.length - 2).join('/');
await service.testExtension('setPubRootDirectories', <String, String>{'arg0': pubRootTest});
- summarySelection = (await service.testExtension('getSelectedSummaryWidget', <String, String>{'objectGroup': group}))! as Map<String, Object>;
+ summarySelection = (await service.testExtension('getSelectedSummaryWidget', <String, String>{'objectGroup': group}))! as Map<String, Object?>;
expect(summarySelection['valueId'], isNotNull);
// We got the Text element instead of the selected RichText element
// because only the RichText element is part of the summary tree.
@@ -1554,7 +1554,7 @@
// Verify tha the regular getSelectedWidget method still returns
// the RichText object not the Text element.
- final Map<String, Object> regularSelection = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
+ final Map<String, Object?> regularSelection = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
expect(service.toObject(regularSelection['valueId']! as String), richTextDiagnostic.value);
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // Test requires --track-widget-creation flag.
@@ -1577,22 +1577,22 @@
service.disposeAllGroups();
await service.testExtension('setPubRootDirectories', <String, String>{});
service.setSelection(elementA, 'my-group');
- final Map<String, Object> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
- final Map<String, Object> creationLocationA = jsonA['creationLocation']! as Map<String, Object>;
+ final Map<String, Object?> jsonA = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+ final Map<String, Object?> creationLocationA = jsonA['creationLocation']! as Map<String, Object?>;
expect(creationLocationA, isNotNull);
final String fileA = creationLocationA['file']! as String;
final int lineA = creationLocationA['line']! as int;
final int columnA = creationLocationA['column']! as int;
- final List<Object> parameterLocationsA = creationLocationA['parameterLocations']! as List<Object>;
+ final List<Object?> parameterLocationsA = creationLocationA['parameterLocations']! as List<Object?>;
service.setSelection(elementB, 'my-group');
- final Map<String, Object> jsonB = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
- final Map<String, Object> creationLocationB = jsonB['creationLocation']! as Map<String, Object>;
+ final Map<String, Object?> jsonB = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+ final Map<String, Object?> creationLocationB = jsonB['creationLocation']! as Map<String, Object?>;
expect(creationLocationB, isNotNull);
final String fileB = creationLocationB['file']! as String;
final int lineB = creationLocationB['line']! as int;
final int columnB = creationLocationB['column']! as int;
- final List<Object> parameterLocationsB = creationLocationB['parameterLocations']! as List<Object>;
+ final List<Object?> parameterLocationsB = creationLocationB['parameterLocations']! as List<Object?>;
expect(fileA, endsWith('widget_inspector_test.dart'));
expect(fileA, equals(fileB));
// We don't hardcode the actual lines the widgets are created on as that
@@ -1602,17 +1602,17 @@
expect(columnA, equals(15));
expect(columnA, equals(columnB));
expect(parameterLocationsA.length, equals(1));
- final Map<String, Object> paramA = parameterLocationsA[0] as Map<String, Object>;
+ final Map<String, Object?> paramA = parameterLocationsA[0]! as Map<String, Object?>;
expect(paramA['name'], equals('data'));
expect(paramA['line'], equals(lineA));
expect(paramA['column'], equals(20));
expect(parameterLocationsB.length, equals(2));
- final Map<String, Object> paramB1 = parameterLocationsB[0] as Map<String, Object>;
+ final Map<String, Object?> paramB1 = parameterLocationsB[0]! as Map<String, Object?>;
expect(paramB1['name'], equals('data'));
expect(paramB1['line'], equals(lineB));
expect(paramB1['column'], equals(20));
- final Map<String, Object> paramB2 = parameterLocationsB[1] as Map<String, Object>;
+ final Map<String, Object?> paramB2 = parameterLocationsB[1]! as Map<String, Object?>;
expect(paramB2['name'], equals('textDirection'));
expect(paramB2['line'], equals(lineB));
expect(paramB2['column'], equals(25));
@@ -1635,8 +1635,8 @@
await service.testExtension('setPubRootDirectories', <String, String>{});
service.setSelection(elementA, 'my-group');
- Map<String, Object> jsonObject = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
- Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+ Map<String, Object?> jsonObject = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+ Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String fileA = creationLocation['file']! as String;
expect(fileA, endsWith('widget_inspector_test.dart'));
@@ -1674,9 +1674,9 @@
).evaluate().first;
service.setSelection(richText, 'my-group');
service.setPubRootDirectories(<String>[pubRootTest]);
- jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object>;
+ jsonObject = json.decode(service.getSelectedWidget(null, 'my-group')) as Map<String, Object?>;
expect(jsonObject, isNot(contains('createdByLocalProject')));
- creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+ creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
// This RichText widget is created by the build method of the Text widget
// thus the creation location is in text.dart not basic.dart
@@ -1697,6 +1697,58 @@
expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), contains('createdByLocalProject'));
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked() || isBrowser); // Test requires --track-widget-creation flag.
+ testWidgets('ext.flutter.inspector.setPubRootDirectories extra args regression test', (WidgetTester tester) async {
+ // Ensure that passing the isolate id as an argument won't break
+ // setPubRootDirectories command.
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: Stack(
+ children: const <Widget>[
+ Text('a'),
+ Text('b', textDirection: TextDirection.ltr),
+ Text('c', textDirection: TextDirection.ltr),
+ ],
+ ),
+ ),
+ );
+ final Element elementA = find.text('a').evaluate().first;
+
+ await service.testExtension('setPubRootDirectories', <String, String>{'isolateId': '34'});
+ service.setSelection(elementA, 'my-group');
+ final Map<String, Object?> jsonObject = (await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+ final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
+ expect(creationLocation, isNotNull);
+ final String fileA = creationLocation['file']! as String;
+ expect(fileA, endsWith('widget_inspector_test.dart'));
+ expect(jsonObject, isNot(contains('createdByLocalProject')));
+ final List<String> segments = Uri.parse(fileA).pathSegments;
+ // Strip a couple subdirectories away to generate a plausible pub root
+ // directory.
+ final String pubRootTest = '/' + segments.take(segments.length - 2).join('/');
+ await service.testExtension('setPubRootDirectories', <String, String>{'arg0': pubRootTest, 'isolateId': '34'});
+
+ service.setSelection(elementA, 'my-group');
+ expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), contains('createdByLocalProject'));
+
+ await service.testExtension('setPubRootDirectories', <String, String>{'arg0': '/invalid/$pubRootTest', 'isolateId': '34'});
+ expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), isNot(contains('createdByLocalProject')));
+
+ await service.testExtension('setPubRootDirectories', <String, String>{'arg0': 'file://$pubRootTest', 'isolateId': '34'});
+ expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), contains('createdByLocalProject'));
+
+ await service.testExtension('setPubRootDirectories', <String, String>{'arg0': '$pubRootTest/different', 'isolateId': '34'});
+ expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), isNot(contains('createdByLocalProject')));
+
+ await service.testExtension('setPubRootDirectories', <String, String>{
+ 'arg0': '/unrelated/$pubRootTest',
+ 'isolateId': '34',
+ 'arg1': 'file://$pubRootTest',
+ });
+
+ expect(await service.testExtension('getSelectedWidget', <String, String>{'objectGroup': 'my-group'}), contains('createdByLocalProject'));
+ }, skip: !WidgetInspectorService.instance.isWidgetCreationTracked() || isBrowser); // Test requires --track-widget-creation flag.
+
testWidgets('ext.flutter.inspector.trackRebuildDirtyWidgets', (WidgetTester tester) async {
service.rebuildCount = 0;
@@ -1705,11 +1757,11 @@
final Element clockDemoElement = find.byType(ClockDemo).evaluate().first;
service.setSelection(clockDemoElement, 'my-group');
- final Map<String, Object> jsonObject = (await service.testExtension(
+ final Map<String, Object?> jsonObject = (await service.testExtension(
'getSelectedWidget',
<String, String>{'objectGroup': 'my-group'},
- ))! as Map<String, Object>;
- final Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+ ))! as Map<String, Object?>;
+ final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String file = creationLocation['file']! as String;
expect(file, endsWith('widget_inspector_test.dart'));
@@ -1901,11 +1953,11 @@
final Element clockDemoElement = find.byType(ClockDemo).evaluate().first;
service.setSelection(clockDemoElement, 'my-group');
- final Map<String, Object> jsonObject = (await service.testExtension(
+ final Map<String, Object?> jsonObject = (await service.testExtension(
'getSelectedWidget',
- <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object>;
- final Map<String, Object> creationLocation =
- jsonObject['creationLocation']! as Map<String, Object>;
+ <String, String>{'objectGroup': 'my-group'}))! as Map<String, Object?>;
+ final Map<String, Object?> creationLocation =
+ jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String file = creationLocation['file']! as String;
expect(file, endsWith('widget_inspector_test.dart'));
@@ -2637,11 +2689,11 @@
);
// Figure out the pubRootDirectory
- final Map<String, Object> jsonObject = (await service.testExtension(
+ final Map<String, Object?> jsonObject = (await service.testExtension(
'getSelectedWidget',
<String, String>{'objectGroup': 'my-group'},
- ))! as Map<String, Object>;
- final Map<String, Object> creationLocation = jsonObject['creationLocation']! as Map<String, Object>;
+ ))! as Map<String, Object?>;
+ final Map<String, Object?> creationLocation = jsonObject['creationLocation']! as Map<String, Object?>;
expect(creationLocation, isNotNull);
final String file = creationLocation['file']! as String;
expect(file, endsWith('widget_inspector_test.dart'));
@@ -2651,22 +2703,22 @@
service.setPubRootDirectories(<String>[pubRootTest]);
final String summary = service.getRootWidgetSummaryTree('foo1');
- final List<dynamic> childrenOfRoot = json.decode(summary)['children'] as List<dynamic>;
- final List<dynamic> childrenOfMaterialApp = childrenOfRoot.first['children'] as List<dynamic>;
- final Map<String, Object> scaffold = childrenOfMaterialApp.first as Map<String, Object>;
+ final List<Object?> childrenOfRoot = json.decode(summary)['children'] as List<Object?>;
+ final List<Object?> childrenOfMaterialApp = (childrenOfRoot.first! as Map<String, Object?>)['children']! as List<Object?>;
+ final Map<String, Object?> scaffold = childrenOfMaterialApp.first! as Map<String, Object?>;
expect(scaffold['description'], 'Scaffold');
final String objectId = scaffold['objectId']! as String;
final String details = service.getDetailsSubtree(objectId, 'foo2');
- final List<dynamic> detailedChildren = json.decode(details)['children'] as List<dynamic>;
+ final List<Object?> detailedChildren = json.decode(details)['children'] as List<Object?>;
- final List<Map<String, Object>> appBars = <Map<String, Object>>[];
- void visitChildren(List<dynamic> children) {
- for (final Map<String, Object> child in children.cast<Map<String, Object>>()) {
+ final List<Map<String, Object?>> appBars = <Map<String, Object?>>[];
+ void visitChildren(List<Object?> children) {
+ for (final Map<String, Object?> child in children.cast<Map<String, Object?>>()) {
if (child['description'] == 'AppBar') {
appBars.add(child);
}
if (child.containsKey('children')) {
- visitChildren(child['children']! as List<dynamic>);
+ visitChildren(child['children']! as List<Object?>);
}
}
}
@@ -2720,8 +2772,8 @@
final Map<String, Object?> json = node.toJsonMap(delegate);
expect(json['callbackExecuted'], true);
expect(json.containsKey('renderObject'), true);
- expect(json['renderObject'], isA<Map<String, dynamic>>());
- final Map<String, dynamic> renderObjectJson = json['renderObject']! as Map<String, dynamic>;
+ expect(json['renderObject'], isA<Map<String, Object?>>());
+ final Map<String, Object?> renderObjectJson = json['renderObject']! as Map<String, Object?>;
expect(renderObjectJson['description'], startsWith('RenderFlex'));
final InspectorSerializationDelegate emptyDelegate =
diff --git a/packages/flutter/test/widgets/widget_inspector_test_utils.dart b/packages/flutter/test/widgets/widget_inspector_test_utils.dart
index be12aed..5ec33fd 100644
--- a/packages/flutter/test/widgets/widget_inspector_test_utils.dart
+++ b/packages/flutter/test/widgets/widget_inspector_test_utils.dart
@@ -30,7 +30,7 @@
}
List<Map<Object, Object?>> getEventsDispatched(String eventKind) {
- return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object>>[]);
+ return eventsDispatched.putIfAbsent(eventKind, () => <Map<Object, Object?>>[]);
}
Iterable<Map<Object, Object?>> getServiceExtensionStateChangedEvents(String extensionName) {
diff --git a/packages/flutter_driver/lib/driver_extension.dart b/packages/flutter_driver/lib/driver_extension.dart
index 13ae916..fce9ad8 100644
--- a/packages/flutter_driver/lib/driver_extension.dart
+++ b/packages/flutter_driver/lib/driver_extension.dart
@@ -24,5 +24,6 @@
/// }
library flutter_driver_extension;
-export 'src/common/create_finder_factory.dart';
+export 'src/common/deserialization_factory.dart';
+export 'src/common/handler_factory.dart';
export 'src/extension/extension.dart';
diff --git a/packages/flutter_driver/lib/flutter_driver.dart b/packages/flutter_driver/lib/flutter_driver.dart
index 46ebb8c..c7eddfe 100644
--- a/packages/flutter_driver/lib/flutter_driver.dart
+++ b/packages/flutter_driver/lib/flutter_driver.dart
@@ -13,6 +13,7 @@
/// Protractor (Angular), Espresso (Android) or Earl Gray (iOS).
library flutter_driver;
+export 'src/common/deserialization_factory.dart';
export 'src/common/diagnostics_tree.dart';
export 'src/common/enum_util.dart';
export 'src/common/error.dart';
diff --git a/packages/flutter_driver/lib/src/common/create_finder_factory.dart b/packages/flutter_driver/lib/src/common/create_finder_factory.dart
deleted file mode 100644
index 36c3d27..0000000
--- a/packages/flutter_driver/lib/src/common/create_finder_factory.dart
+++ /dev/null
@@ -1,117 +0,0 @@
-// 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 'package:flutter_test/flutter_test.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/cupertino.dart';
-
-import 'error.dart';
-import 'find.dart';
-
-/// A factory which creates [Finder]s from [SerializableFinder]s.
-mixin CreateFinderFactory {
- /// Creates the flutter widget finder from [SerializableFinder].
- Finder createFinder(SerializableFinder finder) {
- final String finderType = finder.finderType;
- switch (finderType) {
- case 'ByText':
- return _createByTextFinder(finder as ByText);
- case 'ByTooltipMessage':
- return _createByTooltipMessageFinder(finder as ByTooltipMessage);
- case 'BySemanticsLabel':
- return _createBySemanticsLabelFinder(finder as BySemanticsLabel);
- case 'ByValueKey':
- return _createByValueKeyFinder(finder as ByValueKey);
- case 'ByType':
- return _createByTypeFinder(finder as ByType);
- case 'PageBack':
- return _createPageBackFinder();
- case 'Ancestor':
- return _createAncestorFinder(finder as Ancestor);
- case 'Descendant':
- return _createDescendantFinder(finder as Descendant);
- default:
- throw DriverError('Unsupported search specification type $finderType');
- }
- }
-
- Finder _createByTextFinder(ByText arguments) {
- return find.text(arguments.text);
- }
-
- Finder _createByTooltipMessageFinder(ByTooltipMessage arguments) {
- return find.byElementPredicate((Element element) {
- final Widget widget = element.widget;
- if (widget is Tooltip) {
- return widget.message == arguments.text;
- }
- return false;
- }, description: 'widget with text tooltip "${arguments.text}"');
- }
-
- Finder _createBySemanticsLabelFinder(BySemanticsLabel arguments) {
- return find.byElementPredicate((Element element) {
- if (element is! RenderObjectElement) {
- return false;
- }
- final String? semanticsLabel = element.renderObject.debugSemantics?.label;
- if (semanticsLabel == null) {
- return false;
- }
- final Pattern label = arguments.label;
- return label is RegExp
- ? label.hasMatch(semanticsLabel)
- : label == semanticsLabel;
- }, description: 'widget with semantic label "${arguments.label}"');
- }
-
- Finder _createByValueKeyFinder(ByValueKey arguments) {
- switch (arguments.keyValueType) {
- case 'int':
- return find.byKey(ValueKey<int>(arguments.keyValue as int));
- case 'String':
- return find.byKey(ValueKey<String>(arguments.keyValue as String));
- default:
- throw 'Unsupported ByValueKey type: ${arguments.keyValueType}';
- }
- }
-
- Finder _createByTypeFinder(ByType arguments) {
- return find.byElementPredicate((Element element) {
- return element.widget.runtimeType.toString() == arguments.type;
- }, description: 'widget with runtimeType "${arguments.type}"');
- }
-
- Finder _createPageBackFinder() {
- return find.byElementPredicate((Element element) {
- final Widget widget = element.widget;
- if (widget is Tooltip) {
- return widget.message == 'Back';
- }
- if (widget is CupertinoNavigationBarBackButton) {
- return true;
- }
- return false;
- }, description: 'Material or Cupertino back button');
- }
-
- Finder _createAncestorFinder(Ancestor arguments) {
- final Finder finder = find.ancestor(
- of: createFinder(arguments.of),
- matching: createFinder(arguments.matching),
- matchRoot: arguments.matchRoot,
- );
- return arguments.firstMatchOnly ? finder.first : finder;
- }
-
- Finder _createDescendantFinder(Descendant arguments) {
- final Finder finder = find.descendant(
- of: createFinder(arguments.of),
- matching: createFinder(arguments.matching),
- matchRoot: arguments.matchRoot,
- );
- return arguments.firstMatchOnly ? finder.first : finder;
- }
-}
diff --git a/packages/flutter_driver/lib/src/common/deserialization_factory.dart b/packages/flutter_driver/lib/src/common/deserialization_factory.dart
new file mode 100644
index 0000000..b2e8b95
--- /dev/null
+++ b/packages/flutter_driver/lib/src/common/deserialization_factory.dart
@@ -0,0 +1,70 @@
+// 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 'diagnostics_tree.dart';
+import 'error.dart';
+import 'find.dart';
+import 'frame_sync.dart';
+import 'geometry.dart';
+import 'gesture.dart';
+import 'health.dart';
+import 'layer_tree.dart';
+import 'message.dart';
+import 'render_tree.dart';
+import 'request_data.dart';
+import 'semantics.dart';
+import 'text.dart';
+import 'wait.dart';
+
+/// A factory for deserializing [Finder]s.
+mixin DeserializeFinderFactory {
+ /// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
+ SerializableFinder deserializeFinder(Map<String, String> json) {
+ final String? finderType = json['finderType'];
+ switch (finderType) {
+ case 'ByType': return ByType.deserialize(json);
+ case 'ByValueKey': return ByValueKey.deserialize(json);
+ case 'ByTooltipMessage': return ByTooltipMessage.deserialize(json);
+ case 'BySemanticsLabel': return BySemanticsLabel.deserialize(json);
+ case 'ByText': return ByText.deserialize(json);
+ case 'PageBack': return const PageBack();
+ case 'Descendant': return Descendant.deserialize(json, this);
+ case 'Ancestor': return Ancestor.deserialize(json, this);
+ }
+ throw DriverError('Unsupported search specification type $finderType');
+ }
+}
+
+/// A factory for deserializing [Command]s.
+mixin DeserializeCommandFactory {
+ /// Deserializes the finder from JSON generated by [Command.serialize] or [CommandWithTarget.serialize].
+ Command deserializeCommand(Map<String, String> params, DeserializeFinderFactory finderFactory) {
+ final String? kind = params['command'];
+ switch(kind) {
+ case 'get_health': return GetHealth.deserialize(params);
+ case 'get_layer_tree': return GetLayerTree.deserialize(params);
+ case 'get_render_tree': return GetRenderTree.deserialize(params);
+ case 'enter_text': return EnterText.deserialize(params);
+ case 'get_text': return GetText.deserialize(params, finderFactory);
+ case 'request_data': return RequestData.deserialize(params);
+ case 'scroll': return Scroll.deserialize(params, finderFactory);
+ case 'scrollIntoView': return ScrollIntoView.deserialize(params, finderFactory);
+ case 'set_frame_sync': return SetFrameSync.deserialize(params);
+ case 'set_semantics': return SetSemantics.deserialize(params);
+ case 'set_text_entry_emulation': return SetTextEntryEmulation.deserialize(params);
+ case 'tap': return Tap.deserialize(params, finderFactory);
+ case 'waitFor': return WaitFor.deserialize(params, finderFactory);
+ case 'waitForAbsent': return WaitForAbsent.deserialize(params, finderFactory);
+ case 'waitForCondition': return WaitForCondition.deserialize(params);
+ case 'waitUntilNoTransientCallbacks': return WaitUntilNoTransientCallbacks.deserialize(params);
+ case 'waitUntilNoPendingFrame': return WaitUntilNoPendingFrame.deserialize(params);
+ case 'waitUntilFirstFrameRasterized': return WaitUntilFirstFrameRasterized.deserialize(params);
+ case 'get_semantics_id': return GetSemanticsId.deserialize(params, finderFactory);
+ case 'get_offset': return GetOffset.deserialize(params, finderFactory);
+ case 'get_diagnostics_tree': return GetDiagnosticsTree.deserialize(params, finderFactory);
+ }
+
+ throw DriverError('Unsupported command kind $kind');
+ }
+}
diff --git a/packages/flutter_driver/lib/src/common/diagnostics_tree.dart b/packages/flutter_driver/lib/src/common/diagnostics_tree.dart
index ec4b53d..5bcaa8a 100644
--- a/packages/flutter_driver/lib/src/common/diagnostics_tree.dart
+++ b/packages/flutter_driver/lib/src/common/diagnostics_tree.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'deserialization_factory.dart';
import 'enum_util.dart';
import 'find.dart';
import 'message.dart';
diff --git a/packages/flutter_driver/lib/src/common/find.dart b/packages/flutter_driver/lib/src/common/find.dart
index bdb6ac2..01bd838 100644
--- a/packages/flutter_driver/lib/src/common/find.dart
+++ b/packages/flutter_driver/lib/src/common/find.dart
@@ -6,28 +6,10 @@
import 'package:meta/meta.dart';
+import 'deserialization_factory.dart';
import 'error.dart';
import 'message.dart';
-/// A factory for deserializing [Finder]s.
-mixin DeserializeFinderFactory {
- /// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
- SerializableFinder deserializeFinder(Map<String, String> json) {
- final String? finderType = json['finderType'];
- switch (finderType) {
- case 'ByType': return ByType.deserialize(json);
- case 'ByValueKey': return ByValueKey.deserialize(json);
- case 'ByTooltipMessage': return ByTooltipMessage.deserialize(json);
- case 'BySemanticsLabel': return BySemanticsLabel.deserialize(json);
- case 'ByText': return ByText.deserialize(json);
- case 'PageBack': return const PageBack();
- case 'Descendant': return Descendant.deserialize(json, this);
- case 'Ancestor': return Ancestor.deserialize(json, this);
- }
- throw DriverError('Unsupported search specification type $finderType');
- }
-}
-
const List<Type> _supportedKeyValueTypes = <Type>[String, int];
DriverError _createInvalidKeyValueTypeError(String invalidType) {
diff --git a/packages/flutter_driver/lib/src/common/geometry.dart b/packages/flutter_driver/lib/src/common/geometry.dart
index c8e4a32..aafcd06 100644
--- a/packages/flutter_driver/lib/src/common/geometry.dart
+++ b/packages/flutter_driver/lib/src/common/geometry.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'deserialization_factory.dart';
import 'enum_util.dart';
import 'find.dart';
import 'message.dart';
diff --git a/packages/flutter_driver/lib/src/common/gesture.dart b/packages/flutter_driver/lib/src/common/gesture.dart
index 5a66767..edd87c8 100644
--- a/packages/flutter_driver/lib/src/common/gesture.dart
+++ b/packages/flutter_driver/lib/src/common/gesture.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'deserialization_factory.dart';
import 'find.dart';
import 'message.dart';
diff --git a/packages/flutter_driver/lib/src/common/handler_factory.dart b/packages/flutter_driver/lib/src/common/handler_factory.dart
new file mode 100644
index 0000000..22eb52f
--- /dev/null
+++ b/packages/flutter_driver/lib/src/common/handler_factory.dart
@@ -0,0 +1,492 @@
+// 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:async';
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_driver/src/extension/wait_conditions.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'diagnostics_tree.dart';
+import 'error.dart';
+import 'find.dart';
+import 'frame_sync.dart';
+import 'geometry.dart';
+import 'gesture.dart';
+import 'health.dart';
+import 'layer_tree.dart';
+import 'message.dart';
+import 'render_tree.dart';
+import 'request_data.dart';
+import 'semantics.dart';
+import 'text.dart';
+import 'wait.dart';
+
+/// A factory which creates [Finder]s from [SerializableFinder]s.
+mixin CreateFinderFactory {
+ /// Creates the flutter widget finder from [SerializableFinder].
+ Finder createFinder(SerializableFinder finder) {
+ final String finderType = finder.finderType;
+ switch (finderType) {
+ case 'ByText':
+ return _createByTextFinder(finder as ByText);
+ case 'ByTooltipMessage':
+ return _createByTooltipMessageFinder(finder as ByTooltipMessage);
+ case 'BySemanticsLabel':
+ return _createBySemanticsLabelFinder(finder as BySemanticsLabel);
+ case 'ByValueKey':
+ return _createByValueKeyFinder(finder as ByValueKey);
+ case 'ByType':
+ return _createByTypeFinder(finder as ByType);
+ case 'PageBack':
+ return _createPageBackFinder();
+ case 'Ancestor':
+ return _createAncestorFinder(finder as Ancestor);
+ case 'Descendant':
+ return _createDescendantFinder(finder as Descendant);
+ default:
+ throw DriverError('Unsupported search specification type $finderType');
+ }
+ }
+
+ Finder _createByTextFinder(ByText arguments) {
+ return find.text(arguments.text);
+ }
+
+ Finder _createByTooltipMessageFinder(ByTooltipMessage arguments) {
+ return find.byElementPredicate((Element element) {
+ final Widget widget = element.widget;
+ if (widget is Tooltip) {
+ return widget.message == arguments.text;
+ }
+ return false;
+ }, description: 'widget with text tooltip "${arguments.text}"');
+ }
+
+ Finder _createBySemanticsLabelFinder(BySemanticsLabel arguments) {
+ return find.byElementPredicate((Element element) {
+ if (element is! RenderObjectElement) {
+ return false;
+ }
+ final String? semanticsLabel = element.renderObject.debugSemantics?.label;
+ if (semanticsLabel == null) {
+ return false;
+ }
+ final Pattern label = arguments.label;
+ return label is RegExp
+ ? label.hasMatch(semanticsLabel)
+ : label == semanticsLabel;
+ }, description: 'widget with semantic label "${arguments.label}"');
+ }
+
+ Finder _createByValueKeyFinder(ByValueKey arguments) {
+ switch (arguments.keyValueType) {
+ case 'int':
+ return find.byKey(ValueKey<int>(arguments.keyValue as int));
+ case 'String':
+ return find.byKey(ValueKey<String>(arguments.keyValue as String));
+ default:
+ throw 'Unsupported ByValueKey type: ${arguments.keyValueType}';
+ }
+ }
+
+ Finder _createByTypeFinder(ByType arguments) {
+ return find.byElementPredicate((Element element) {
+ return element.widget.runtimeType.toString() == arguments.type;
+ }, description: 'widget with runtimeType "${arguments.type}"');
+ }
+
+ Finder _createPageBackFinder() {
+ return find.byElementPredicate((Element element) {
+ final Widget widget = element.widget;
+ if (widget is Tooltip) {
+ return widget.message == 'Back';
+ }
+ if (widget is CupertinoNavigationBarBackButton) {
+ return true;
+ }
+ return false;
+ }, description: 'Material or Cupertino back button');
+ }
+
+ Finder _createAncestorFinder(Ancestor arguments) {
+ final Finder finder = find.ancestor(
+ of: createFinder(arguments.of),
+ matching: createFinder(arguments.matching),
+ matchRoot: arguments.matchRoot,
+ );
+ return arguments.firstMatchOnly ? finder.first : finder;
+ }
+
+ Finder _createDescendantFinder(Descendant arguments) {
+ final Finder finder = find.descendant(
+ of: createFinder(arguments.of),
+ matching: createFinder(arguments.matching),
+ matchRoot: arguments.matchRoot,
+ );
+ return arguments.firstMatchOnly ? finder.first : finder;
+ }
+}
+
+/// A factory for [Command] handlers.
+mixin CommandHandlerFactory {
+ /// With [_frameSync] enabled, Flutter Driver will wait to perform an action
+ /// until there are no pending frames in the app under test.
+ bool _frameSync = true;
+
+ /// Gets [DataHandler] for result delivery.
+ @protected
+ DataHandler? getDataHandler() => null;
+
+ /// Registers text input emulation.
+ @protected
+ void registerTextInput() {
+ _testTextInput.register();
+ }
+
+ final TestTextInput _testTextInput = TestTextInput();
+
+ /// Deserializes the finder from JSON generated by [Command.serialize] or [CommandWithTarget.serialize].
+ Future<Result?> handleCommand(Command command, WidgetController prober, CreateFinderFactory finderFactory) {
+ switch(command.kind) {
+ case 'get_health': return _getHealth(command);
+ case 'get_layer_tree': return _getLayerTree(command);
+ case 'get_render_tree': return _getRenderTree(command);
+ case 'enter_text': return _enterText(command);
+ case 'get_text': return _getText(command, finderFactory);
+ case 'request_data': return _requestData(command);
+ case 'scroll': return _scroll(command, prober, finderFactory);
+ case 'scrollIntoView': return _scrollIntoView(command, finderFactory);
+ case 'set_frame_sync': return _setFrameSync(command);
+ case 'set_semantics': return _setSemantics(command);
+ case 'set_text_entry_emulation': return _setTextEntryEmulation(command);
+ case 'tap': return _tap(command, prober, finderFactory);
+ case 'waitFor': return _waitFor(command, finderFactory);
+ case 'waitForAbsent': return _waitForAbsent(command, finderFactory);
+ case 'waitForCondition': return _waitForCondition(command);
+ case 'waitUntilNoTransientCallbacks': return _waitUntilNoTransientCallbacks(command);
+ case 'waitUntilNoPendingFrame': return _waitUntilNoPendingFrame(command);
+ case 'waitUntilFirstFrameRasterized': return _waitUntilFirstFrameRasterized(command);
+ case 'get_semantics_id': return _getSemanticsId(command, finderFactory);
+ case 'get_offset': return _getOffset(command, finderFactory);
+ case 'get_diagnostics_tree': return _getDiagnosticsTree(command, finderFactory);
+ }
+
+ throw DriverError('Unsupported command kind ${command.kind}');
+ }
+
+ Future<Health> _getHealth(Command command) async => const Health(HealthStatus.ok);
+
+ Future<LayerTree> _getLayerTree(Command command) async {
+ return LayerTree(RendererBinding.instance?.renderView.debugLayer?.toStringDeep());
+ }
+
+ Future<RenderTree> _getRenderTree(Command command) async {
+ return RenderTree(RendererBinding.instance?.renderView.toStringDeep());
+ }
+
+ Future<EnterTextResult> _enterText(Command command) async {
+ if (!_testTextInput.isRegistered) {
+ throw 'Unable to fulfill `FlutterDriver.enterText`. Text emulation is '
+ 'disabled. You can enable it using `FlutterDriver.setTextEntryEmulation`.';
+ }
+ final EnterText enterTextCommand = command as EnterText;
+ _testTextInput.enterText(enterTextCommand.text);
+ return const EnterTextResult();
+ }
+
+ Future<RequestDataResult> _requestData(Command command) async {
+ final RequestData requestDataCommand = command as RequestData;
+ final DataHandler? dataHandler = getDataHandler();
+ return RequestDataResult(dataHandler == null
+ ? 'No requestData Extension registered'
+ : await dataHandler(requestDataCommand.message));
+ }
+
+ Future<SetFrameSyncResult> _setFrameSync(Command command) async {
+ final SetFrameSync setFrameSyncCommand = command as SetFrameSync;
+ _frameSync = setFrameSyncCommand.enabled;
+ return const SetFrameSyncResult();
+ }
+
+ Future<TapResult> _tap(Command command, WidgetController prober, CreateFinderFactory finderFactory) async {
+ final Tap tapCommand = command as Tap;
+ final Finder computedFinder = await waitForElement(
+ finderFactory.createFinder(tapCommand.finder).hitTestable(),
+ );
+ await prober.tap(computedFinder);
+ return const TapResult();
+ }
+
+ Future<WaitForResult> _waitFor(Command command, CreateFinderFactory finderFactory) async {
+ final WaitFor waitForCommand = command as WaitFor;
+ await waitForElement(finderFactory.createFinder(waitForCommand.finder));
+ return const WaitForResult();
+ }
+
+ Future<WaitForAbsentResult> _waitForAbsent(Command command, CreateFinderFactory finderFactory) async {
+ final WaitForAbsent waitForAbsentCommand = command as WaitForAbsent;
+ await waitForAbsentElement(finderFactory.createFinder(waitForAbsentCommand.finder));
+ return const WaitForAbsentResult();
+ }
+
+ Future<Result?> _waitForCondition(Command command) async {
+ assert(command != null);
+ final WaitForCondition waitForConditionCommand = command as WaitForCondition;
+ final WaitCondition condition = deserializeCondition(waitForConditionCommand.condition);
+ await condition.wait();
+ return null;
+ }
+
+ @Deprecated(
+ 'This method has been deprecated in favor of _waitForCondition. '
+ 'This feature was deprecated after v1.9.3.'
+ )
+ Future<Result?> _waitUntilNoTransientCallbacks(Command command) async {
+ if (SchedulerBinding.instance!.transientCallbackCount != 0)
+ await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+ return null;
+ }
+
+ /// Returns a future that waits until no pending frame is scheduled (frame is synced).
+ ///
+ /// Specifically, it checks:
+ /// * Whether the count of transient callbacks is zero.
+ /// * Whether there's no pending request for scheduling a new frame.
+ ///
+ /// We consider the frame is synced when both conditions are met.
+ ///
+ /// This method relies on a Flutter Driver mechanism called "frame sync",
+ /// which waits for transient animations to finish. Persistent animations will
+ /// cause this to wait forever.
+ ///
+ /// If a test needs to interact with the app while animations are running, it
+ /// should avoid this method and instead disable the frame sync using
+ /// `set_frame_sync` method. See [FlutterDriver.runUnsynchronized] for more
+ /// details on how to do this. Note, disabling frame sync will require the
+ /// test author to use some other method to avoid flakiness.
+ ///
+ /// This method has been deprecated in favor of [_waitForCondition].
+ @Deprecated(
+ 'This method has been deprecated in favor of _waitForCondition. '
+ 'This feature was deprecated after v1.9.3.'
+ )
+ Future<Result?> _waitUntilNoPendingFrame(Command command) async {
+ await _waitUntilFrame(() {
+ return SchedulerBinding.instance!.transientCallbackCount == 0
+ && !SchedulerBinding.instance!.hasScheduledFrame;
+ });
+ return null;
+ }
+
+ Future<GetSemanticsIdResult> _getSemanticsId(Command command, CreateFinderFactory finderFactory) async {
+ final GetSemanticsId semanticsCommand = command as GetSemanticsId;
+ final Finder target = await waitForElement(finderFactory.createFinder(semanticsCommand.finder));
+ final Iterable<Element> elements = target.evaluate();
+ if (elements.length > 1) {
+ throw StateError('Found more than one element with the same ID: $elements');
+ }
+ final Element element = elements.single;
+ RenderObject? renderObject = element.renderObject;
+ SemanticsNode? node;
+ while (renderObject != null && node == null) {
+ node = renderObject.debugSemantics;
+ renderObject = renderObject.parent as RenderObject?;
+ }
+ if (node == null)
+ throw StateError('No semantics data found');
+ return GetSemanticsIdResult(node.id);
+ }
+
+ Future<GetOffsetResult> _getOffset(Command command, CreateFinderFactory finderFactory) async {
+ final GetOffset getOffsetCommand = command as GetOffset;
+ final Finder finder = await waitForElement(finderFactory.createFinder(getOffsetCommand.finder));
+ final Element element = finder.evaluate().single;
+ final RenderBox box = (element.renderObject as RenderBox?)!;
+ Offset localPoint;
+ switch (getOffsetCommand.offsetType) {
+ case OffsetType.topLeft:
+ localPoint = Offset.zero;
+ break;
+ case OffsetType.topRight:
+ localPoint = box.size.topRight(Offset.zero);
+ break;
+ case OffsetType.bottomLeft:
+ localPoint = box.size.bottomLeft(Offset.zero);
+ break;
+ case OffsetType.bottomRight:
+ localPoint = box.size.bottomRight(Offset.zero);
+ break;
+ case OffsetType.center:
+ localPoint = box.size.center(Offset.zero);
+ break;
+ }
+ final Offset globalPoint = box.localToGlobal(localPoint);
+ return GetOffsetResult(dx: globalPoint.dx, dy: globalPoint.dy);
+ }
+
+ Future<DiagnosticsTreeResult> _getDiagnosticsTree(Command command, CreateFinderFactory finderFactory) async {
+ final GetDiagnosticsTree diagnosticsCommand = command as GetDiagnosticsTree;
+ final Finder finder = await waitForElement(finderFactory.createFinder(diagnosticsCommand.finder));
+ final Element element = finder.evaluate().single;
+ DiagnosticsNode diagnosticsNode;
+ switch (diagnosticsCommand.diagnosticsType) {
+ case DiagnosticsType.renderObject:
+ diagnosticsNode = element.renderObject!.toDiagnosticsNode();
+ break;
+ case DiagnosticsType.widget:
+ diagnosticsNode = element.toDiagnosticsNode();
+ break;
+ }
+ return DiagnosticsTreeResult(diagnosticsNode.toJsonMap(DiagnosticsSerializationDelegate(
+ subtreeDepth: diagnosticsCommand.subtreeDepth,
+ includeProperties: diagnosticsCommand.includeProperties,
+ )));
+ }
+
+ Future<ScrollResult> _scroll(Command command, WidgetController _prober, CreateFinderFactory finderFactory) async {
+ final Scroll scrollCommand = command as Scroll;
+ final Finder target = await waitForElement(finderFactory.createFinder(scrollCommand.finder));
+ final int totalMoves = scrollCommand.duration.inMicroseconds * scrollCommand.frequency ~/ Duration.microsecondsPerSecond;
+ final Offset delta = Offset(scrollCommand.dx, scrollCommand.dy) / totalMoves.toDouble();
+ final Duration pause = scrollCommand.duration ~/ totalMoves;
+ final Offset startLocation = _prober.getCenter(target);
+ Offset currentLocation = startLocation;
+ final TestPointer pointer = TestPointer(1);
+ _prober.binding.handlePointerEvent(pointer.down(startLocation));
+ await Future<void>.value(); // so that down and move don't happen in the same microtask
+ for (int moves = 0; moves < totalMoves; moves += 1) {
+ currentLocation = currentLocation + delta;
+ _prober.binding.handlePointerEvent(pointer.move(currentLocation));
+ await Future<void>.delayed(pause);
+ }
+ _prober.binding.handlePointerEvent(pointer.up());
+
+ return const ScrollResult();
+ }
+
+ Future<ScrollResult> _scrollIntoView(Command command, CreateFinderFactory finderFactory) async {
+ final ScrollIntoView scrollIntoViewCommand = command as ScrollIntoView;
+ final Finder target = await waitForElement(finderFactory.createFinder(scrollIntoViewCommand.finder));
+ await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: scrollIntoViewCommand.alignment);
+ return const ScrollResult();
+ }
+
+ Future<GetTextResult> _getText(Command command, CreateFinderFactory finderFactory) async {
+ final GetText getTextCommand = command as GetText;
+ final Finder target = await waitForElement(finderFactory.createFinder(getTextCommand.finder));
+
+ final Widget widget = target.evaluate().single.widget;
+ String? text;
+
+ if (widget.runtimeType == Text) {
+ text = (widget as Text).data;
+ } else if (widget.runtimeType == RichText) {
+ final RichText richText = widget as RichText;
+ if (richText.text.runtimeType == TextSpan) {
+ text = (richText.text as TextSpan).text;
+ }
+ } else if (widget.runtimeType == TextField) {
+ text = (widget as TextField).controller?.text;
+ } else if (widget.runtimeType == TextFormField) {
+ text = (widget as TextFormField).controller?.text;
+ } else if (widget.runtimeType == EditableText) {
+ text = (widget as EditableText).controller.text;
+ }
+
+ if (text == null) {
+ throw UnsupportedError('Type ${widget.runtimeType.toString()} is currently not supported by getText');
+ }
+
+ return GetTextResult(text);
+ }
+
+ Future<SetTextEntryEmulationResult> _setTextEntryEmulation(Command command) async {
+ final SetTextEntryEmulation setTextEntryEmulationCommand = command as SetTextEntryEmulation;
+ if (setTextEntryEmulationCommand.enabled) {
+ _testTextInput.register();
+ } else {
+ _testTextInput.unregister();
+ }
+ return const SetTextEntryEmulationResult();
+ }
+
+ SemanticsHandle? _semantics;
+ bool get _semanticsIsEnabled => RendererBinding.instance!.pipelineOwner.semanticsOwner != null;
+
+ Future<SetSemanticsResult> _setSemantics(Command command) async {
+ final SetSemantics setSemanticsCommand = command as SetSemantics;
+ final bool semanticsWasEnabled = _semanticsIsEnabled;
+ if (setSemanticsCommand.enabled && _semantics == null) {
+ _semantics = RendererBinding.instance!.pipelineOwner.ensureSemantics();
+ if (!semanticsWasEnabled) {
+ // wait for the first frame where semantics is enabled.
+ final Completer<void> completer = Completer<void>();
+ SchedulerBinding.instance!.addPostFrameCallback((Duration d) {
+ completer.complete();
+ });
+ await completer.future;
+ }
+ } else if (!setSemanticsCommand.enabled && _semantics != null) {
+ _semantics!.dispose();
+ _semantics = null;
+ }
+ return SetSemanticsResult(semanticsWasEnabled != _semanticsIsEnabled);
+ }
+
+ // This can be used to wait for the first frame being rasterized during app launch.
+ @Deprecated(
+ 'This method has been deprecated in favor of _waitForCondition. '
+ 'This feature was deprecated after v1.9.3.'
+ )
+ Future<Result?> _waitUntilFirstFrameRasterized(Command command) async {
+ await WidgetsBinding.instance!.waitUntilFirstFrameRasterized;
+ return null;
+ }
+
+ /// Runs `finder` repeatedly until it finds one or more [Element]s.
+ Future<Finder> waitForElement(Finder finder) async {
+ if (_frameSync)
+ await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+
+ await _waitUntilFrame(() => finder.evaluate().isNotEmpty);
+
+ if (_frameSync)
+ await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+
+ return finder;
+ }
+
+ /// Runs `finder` repeatedly until it finds zero [Element]s.
+ Future<Finder> waitForAbsentElement(Finder finder) async {
+ if (_frameSync)
+ await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+
+ await _waitUntilFrame(() => finder.evaluate().isEmpty);
+
+ if (_frameSync)
+ await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
+
+ return finder;
+ }
+
+ // Waits until at the end of a frame the provided [condition] is [true].
+ Future<void> _waitUntilFrame(bool condition(), [ Completer<void>? completer ]) {
+ completer ??= Completer<void>();
+ if (!condition()) {
+ SchedulerBinding.instance!.addPostFrameCallback((Duration timestamp) {
+ _waitUntilFrame(condition, completer);
+ });
+ } else {
+ completer.complete();
+ }
+ return completer.future;
+ }
+}
diff --git a/packages/flutter_driver/lib/src/common/text.dart b/packages/flutter_driver/lib/src/common/text.dart
index 6ccaf84..a394fbe 100644
--- a/packages/flutter_driver/lib/src/common/text.dart
+++ b/packages/flutter_driver/lib/src/common/text.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'deserialization_factory.dart';
import 'find.dart';
import 'message.dart';
diff --git a/packages/flutter_driver/lib/src/extension/extension.dart b/packages/flutter_driver/lib/src/extension/extension.dart
index 44bc962..324decc 100644
--- a/packages/flutter_driver/lib/src/extension/extension.dart
+++ b/packages/flutter_driver/lib/src/extension/extension.dart
@@ -9,33 +9,21 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/rendering.dart' show RendererBinding, SemanticsHandle;
+import 'package:flutter/rendering.dart' show RendererBinding;
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
-import '../common/create_finder_factory.dart';
-import '../common/diagnostics_tree.dart';
+import '../common/deserialization_factory.dart';
import '../common/error.dart';
import '../common/find.dart';
-import '../common/frame_sync.dart';
-import '../common/geometry.dart';
-import '../common/gesture.dart';
-import '../common/health.dart';
-import '../common/layer_tree.dart';
+import '../common/handler_factory.dart';
import '../common/message.dart';
-import '../common/render_tree.dart';
-import '../common/request_data.dart';
-import '../common/semantics.dart';
-import '../common/text.dart';
-import '../common/wait.dart';
import '_extension_io.dart' if (dart.library.html) '_extension_web.dart';
-import 'wait_conditions.dart';
const String _extensionMethodName = 'driver';
-const String _extensionMethod = 'ext.flutter.$_extensionMethodName';
/// Signature for the handler passed to [enableFlutterDriverExtension].
///
@@ -44,16 +32,17 @@
typedef DataHandler = Future<String> Function(String? message);
class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
- _DriverBinding(this._handler, this._silenceErrors, this.finders);
+ _DriverBinding(this._handler, this._silenceErrors, this.finders, this.commands);
final DataHandler? _handler;
final bool _silenceErrors;
final List<FinderExtension>? finders;
+ final List<CommandExtension>? commands;
@override
void initServiceExtensions() {
super.initServiceExtensions();
- final FlutterDriverExtension extension = FlutterDriverExtension(_handler, _silenceErrors, finders: finders ?? const <FinderExtension>[]);
+ final FlutterDriverExtension extension = FlutterDriverExtension(_handler, _silenceErrors, finders: finders ?? const <FinderExtension>[], commands: commands ?? const <CommandExtension>[]);
registerServiceExtension(
name: _extensionMethodName,
callback: extension.call,
@@ -89,24 +78,36 @@
/// will still be returned in the `response` field of the result JSON along
/// with an `isError` boolean.
///
-/// The `finders` parameter are used to add custom finders, as in the following example.
+/// The `finders` and `commands` parameters are optional and used to add custom
+/// finders or commands, as in the following example.
///
/// ```dart main
/// void main() {
-/// enableFlutterDriverExtension(finders: <FinderExtension>[ SomeFinderExtension() ]);
+/// enableFlutterDriverExtension(
+/// finders: <FinderExtension>[ SomeFinderExtension() ],
+/// commands: <CommandExtension>[ SomeCommandExtension() ],
+/// );
///
/// app.main();
/// }
/// ```
///
/// ```dart
-/// class Some extends SerializableFinder {
-/// const Some(this.title);
+/// driver.sendCommand(SomeCommand(ByValueKey('Button'), 7));
+/// ```
+///
+/// Note: SomeFinder and SomeFinderExtension must be placed in different files
+/// to avoid `dart:ui` import issue. Imports relative to `dart:ui` can't be
+/// accessed from host runner, where flutter runtime is not accessible.
+///
+/// ```dart
+/// class SomeFinder extends SerializableFinder {
+/// const SomeFinder(this.title);
///
/// final String title;
///
/// @override
-/// String get finderType => 'Some';
+/// String get finderType => 'SomeFinder';
///
/// @override
/// Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
@@ -118,14 +119,14 @@
/// ```dart
/// class SomeFinderExtension extends FinderExtension {
///
-/// String get finderType => 'Some';
+/// String get finderType => 'SomeFinder';
///
/// SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory) {
-/// return Some(json['title']);
+/// return SomeFinder(json['title']);
/// }
///
/// Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) {
-/// Some someFinder = finder as Some;
+/// Some someFinder = finder as SomeFinder;
///
/// return find.byElementPredicate((Element element) {
/// final Widget widget = element.widget;
@@ -138,9 +139,87 @@
/// }
/// ```
///
-void enableFlutterDriverExtension({ DataHandler? handler, bool silenceErrors = false, List<FinderExtension>? finders}) {
+/// Note: SomeCommand, SomeResult and SomeCommandExtension must be placed in
+/// different files to avoid `dart:ui` import issue. Imports relative to `dart:ui`
+/// can't be accessed from host runner, where flutter runtime is not accessible.
+///
+/// ```dart
+/// class SomeCommand extends CommandWithTarget {
+/// SomeCommand(SerializableFinder finder, this.times, {Duration? timeout})
+/// : super(finder, timeout: timeout);
+///
+/// SomeCommand.deserialize(Map<String, String> json, DeserializeFinderFactory finderFactory)
+/// : times = int.parse(json['times']!),
+/// super.deserialize(json, finderFactory);
+///
+/// @override
+/// Map<String, String> serialize() {
+/// return super.serialize()..addAll(<String, String>{'times': '$times'});
+/// }
+///
+/// @override
+/// String get kind => 'SomeCommand';
+///
+/// final int times;
+/// }
+///```
+///
+/// ```dart
+/// class SomeCommandResult extends Result {
+/// const SomeCommandResult(this.resultParam);
+///
+/// final String resultParam;
+///
+/// @override
+/// Map<String, dynamic> toJson() {
+/// return <String, dynamic>{
+/// 'resultParam': resultParam,
+/// };
+/// }
+/// }
+/// ```
+///
+/// ```dart
+/// class SomeCommandExtension extends CommandExtension {
+/// @override
+/// String get commandKind => 'SomeCommand';
+///
+/// @override
+/// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+/// final SomeCommand someCommand = command as SomeCommand;
+///
+/// // Deserialize [Finder]:
+/// final Finder finder = finderFactory.createFinder(stubCommand.finder);
+///
+/// // Wait for [Element]:
+/// handlerFactory.waitForElement(finder);
+///
+/// // Alternatively, wait for [Element] absence:
+/// handlerFactory.waitForAbsentElement(finder);
+///
+/// // Submit known [Command]s:
+/// for (int index = 0; i < someCommand.times; index++) {
+/// await handlerFactory.handleCommand(Tap(someCommand.finder), prober, finderFactory);
+/// }
+///
+/// // Alternatively, use [WidgetController]:
+/// for (int index = 0; i < stubCommand.times; index++) {
+/// await prober.tap(finder);
+/// }
+///
+/// return const SomeCommandResult('foo bar');
+/// }
+///
+/// @override
+/// Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) {
+/// return SomeCommand.deserialize(params, finderFactory);
+/// }
+/// }
+/// ```
+///
+void enableFlutterDriverExtension({ DataHandler? handler, bool silenceErrors = false, List<FinderExtension>? finders, List<CommandExtension>? commands}) {
assert(WidgetsBinding.instance == null);
- _DriverBinding(handler, silenceErrors, finders ?? <FinderExtension>[]);
+ _DriverBinding(handler, silenceErrors, finders ?? <FinderExtension>[], commands ?? <CommandExtension>[]);
assert(WidgetsBinding.instance is _DriverBinding);
}
@@ -150,107 +229,119 @@
/// Signature for functions that deserialize a JSON map to a command object.
typedef CommandDeserializerCallback = Command Function(Map<String, String> params);
-/// Used to expand the new Finder
+/// Used to expand the new [Finder].
abstract class FinderExtension {
/// Identifies the type of finder to be used by the driver extension.
String get finderType;
/// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
- /// [finderFactory] could be used to deserialize nested finders.
+ ///
+ /// Use [finderFactory] to deserialize nested [Finder]s.
+ ///
+ /// See also:
+ /// * [Ancestor], a finder that uses other [Finder]s as parameters.
SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory);
/// Signature for functions that run the given finder and return the [Element]
/// found, if any, or null otherwise.
- /// [finderFactory] could be used to create nested finders.
+ ///
+ /// Call [finderFactory] to create known, nested [Finder]s from [SerializableFinder]s.
Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory);
}
+/// Used to expand the new [Command].
+///
+/// See also:
+/// * [CommandWithTarget], a base class for [Command]s with [Finder]s.
+abstract class CommandExtension {
+
+ /// Identifies the type of command to be used by the driver extension.
+ String get commandKind;
+
+ /// Deserializes the command from JSON generated by [Command.serialize].
+ ///
+ /// Use [finderFactory] to deserialize nested [Finder]s.
+ /// Usually used for [CommandWithTarget]s.
+ ///
+ /// Call [commandFactory] to deserialize commands specified as parameters.
+ ///
+ /// See also:
+ /// * [CommandWithTarget], a base class for commands with target finders.
+ /// * [Tap], a command that uses [Finder]s as parameter.
+ Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory);
+
+ /// Calls action for given [command].
+ /// Returns action [Result].
+ /// Invoke [prober] functions to perform widget actions.
+ /// Use [finderFactory] to create [Finder]s from [SerializableFinder].
+ /// Call [handlerFactory] to invoke other [Command]s or [CommandWithTarget]s.
+ ///
+ /// The following example shows invoking nested command with [handlerFactory].
+ ///
+ /// ```dart
+ /// @override
+ /// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+ /// final StubNestedCommand stubCommand = command as StubNestedCommand;
+ /// for (int index = 0; i < stubCommand.times; index++) {
+ /// await handlerFactory.handleCommand(Tap(stubCommand.finder), prober, finderFactory);
+ /// }
+ /// return const StubCommandResult('stub response');
+ /// }
+ /// ```
+ ///
+ /// Check the example below for direct [WidgetController] usage with [prober]:
+ ///
+ /// ```dart
+ /// @override
+ /// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+ /// final StubProberCommand stubCommand = command as StubProberCommand;
+ /// for (int index = 0; i < stubCommand.times; index++) {
+ /// await prober.tap(finderFactory.createFinder(stubCommand.finder));
+ /// }
+ /// return const StubCommandResult('stub response');
+ /// }
+ /// ```
+ Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory);
+}
+
/// The class that manages communication between a Flutter Driver test and the
/// application being remote-controlled, on the application side.
///
/// This is not normally used directly. It is instantiated automatically when
/// calling [enableFlutterDriverExtension].
@visibleForTesting
-class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory {
+class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory, DeserializeCommandFactory, CommandHandlerFactory {
/// Creates an object to manage a Flutter Driver connection.
FlutterDriverExtension(
this._requestDataHandler,
this._silenceErrors, {
List<FinderExtension> finders = const <FinderExtension>[],
+ List<CommandExtension> commands = const <CommandExtension>[],
}) : assert(finders != null) {
- _testTextInput.register();
-
- _commandHandlers.addAll(<String, CommandHandlerCallback>{
- 'get_health': _getHealth,
- 'get_layer_tree': _getLayerTree,
- 'get_render_tree': _getRenderTree,
- 'enter_text': _enterText,
- 'get_text': _getText,
- 'request_data': _requestData,
- 'scroll': _scroll,
- 'scrollIntoView': _scrollIntoView,
- 'set_frame_sync': _setFrameSync,
- 'set_semantics': _setSemantics,
- 'set_text_entry_emulation': _setTextEntryEmulation,
- 'tap': _tap,
- 'waitFor': _waitFor,
- 'waitForAbsent': _waitForAbsent,
- 'waitForCondition': _waitForCondition,
- 'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks,
- 'waitUntilNoPendingFrame': _waitUntilNoPendingFrame,
- 'waitUntilFirstFrameRasterized': _waitUntilFirstFrameRasterized,
- 'get_semantics_id': _getSemanticsId,
- 'get_offset': _getOffset,
- 'get_diagnostics_tree': _getDiagnosticsTree,
- });
-
- _commandDeserializers.addAll(<String, CommandDeserializerCallback>{
- 'get_health': (Map<String, String> params) => GetHealth.deserialize(params),
- 'get_layer_tree': (Map<String, String> params) => GetLayerTree.deserialize(params),
- 'get_render_tree': (Map<String, String> params) => GetRenderTree.deserialize(params),
- 'enter_text': (Map<String, String> params) => EnterText.deserialize(params),
- 'get_text': (Map<String, String> params) => GetText.deserialize(params, this),
- 'request_data': (Map<String, String> params) => RequestData.deserialize(params),
- 'scroll': (Map<String, String> params) => Scroll.deserialize(params, this),
- 'scrollIntoView': (Map<String, String> params) => ScrollIntoView.deserialize(params, this),
- 'set_frame_sync': (Map<String, String> params) => SetFrameSync.deserialize(params),
- 'set_semantics': (Map<String, String> params) => SetSemantics.deserialize(params),
- 'set_text_entry_emulation': (Map<String, String> params) => SetTextEntryEmulation.deserialize(params),
- 'tap': (Map<String, String> params) => Tap.deserialize(params, this),
- 'waitFor': (Map<String, String> params) => WaitFor.deserialize(params, this),
- 'waitForAbsent': (Map<String, String> params) => WaitForAbsent.deserialize(params, this),
- 'waitForCondition': (Map<String, String> params) => WaitForCondition.deserialize(params),
- 'waitUntilNoTransientCallbacks': (Map<String, String> params) => WaitUntilNoTransientCallbacks.deserialize(params),
- 'waitUntilNoPendingFrame': (Map<String, String> params) => WaitUntilNoPendingFrame.deserialize(params),
- 'waitUntilFirstFrameRasterized': (Map<String, String> params) => WaitUntilFirstFrameRasterized.deserialize(params),
- 'get_semantics_id': (Map<String, String> params) => GetSemanticsId.deserialize(params, this),
- 'get_offset': (Map<String, String> params) => GetOffset.deserialize(params, this),
- 'get_diagnostics_tree': (Map<String, String> params) => GetDiagnosticsTree.deserialize(params, this),
- });
+ registerTextInput();
for(final FinderExtension finder in finders) {
_finderExtensions[finder.finderType] = finder;
}
+
+ for(final CommandExtension command in commands) {
+ _commandExtensions[command.commandKind] = command;
+ }
}
- final TestTextInput _testTextInput = TestTextInput();
+ final WidgetController _prober = LiveWidgetController(WidgetsBinding.instance!);
final DataHandler? _requestDataHandler;
+
final bool _silenceErrors;
void _log(String message) {
driverLog('FlutterDriverExtension', message);
}
- final WidgetController _prober = LiveWidgetController(WidgetsBinding.instance!);
- final Map<String, CommandHandlerCallback> _commandHandlers = <String, CommandHandlerCallback>{};
- final Map<String, CommandDeserializerCallback> _commandDeserializers = <String, CommandDeserializerCallback>{};
final Map<String, FinderExtension> _finderExtensions = <String, FinderExtension>{};
-
- /// With [_frameSync] enabled, Flutter Driver will wait to perform an action
- /// until there are no pending frames in the app under test.
- bool _frameSync = true;
+ final Map<String, CommandExtension> _commandExtensions = <String, CommandExtension>{};
/// Processes a driver command configured by [params] and returns a result
/// as an arbitrary JSON object.
@@ -266,15 +357,10 @@
Future<Map<String, dynamic>> call(Map<String, String> params) async {
final String commandKind = params['command']!;
try {
- final CommandHandlerCallback commandHandler = _commandHandlers[commandKind]!;
- final CommandDeserializerCallback commandDeserializer =
- _commandDeserializers[commandKind]!;
- if (commandHandler == null || commandDeserializer == null)
- throw 'Extension $_extensionMethod does not support command $commandKind';
- final Command command = commandDeserializer(params);
+ final Command command = deserializeCommand(params, this);
assert(WidgetsBinding.instance!.isRootWidgetAttached || !command.requiresRootWidgetAttached,
'No root widget is attached; have you remembered to call runApp()?');
- Future<Result?> responseFuture = commandHandler(command);
+ Future<Result?> responseFuture = handleCommand(command, _prober, this);
if (command.timeout != null)
responseFuture = responseFuture.timeout(command.timeout ?? Duration.zero);
final Result? response = await responseFuture;
@@ -298,65 +384,6 @@
};
}
- Future<Health> _getHealth(Command command) async => const Health(HealthStatus.ok);
-
- Future<LayerTree> _getLayerTree(Command command) async {
- return LayerTree(RendererBinding.instance?.renderView.debugLayer?.toStringDeep());
- }
-
- Future<RenderTree> _getRenderTree(Command command) async {
- return RenderTree(RendererBinding.instance?.renderView.toStringDeep());
- }
-
- // This can be used to wait for the first frame being rasterized during app launch.
- @Deprecated(
- 'This method has been deprecated in favor of _waitForCondition. '
- 'This feature was deprecated after v1.9.3.'
- )
- Future<Result?> _waitUntilFirstFrameRasterized(Command command) async {
- await WidgetsBinding.instance!.waitUntilFirstFrameRasterized;
- return null;
- }
-
- // Waits until at the end of a frame the provided [condition] is [true].
- Future<void> _waitUntilFrame(bool condition(), [ Completer<void>? completer ]) {
- completer ??= Completer<void>();
- if (!condition()) {
- SchedulerBinding.instance!.addPostFrameCallback((Duration timestamp) {
- _waitUntilFrame(condition, completer);
- });
- } else {
- completer.complete();
- }
- return completer.future;
- }
-
- /// Runs `finder` repeatedly until it finds one or more [Element]s.
- Future<Finder> _waitForElement(Finder finder) async {
- if (_frameSync)
- await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-
- await _waitUntilFrame(() => finder.evaluate().isNotEmpty);
-
- if (_frameSync)
- await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-
- return finder;
- }
-
- /// Runs `finder` repeatedly until it finds zero [Element]s.
- Future<Finder> _waitForAbsentElement(Finder finder) async {
- if (_frameSync)
- await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-
- await _waitUntilFrame(() => finder.evaluate().isEmpty);
-
- if (_frameSync)
- await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
-
- return finder;
- }
-
@override
SerializableFinder deserializeFinder(Map<String, String> json) {
final String? finderType = json['finderType'];
@@ -369,258 +396,37 @@
@override
Finder createFinder(SerializableFinder finder) {
- if (_finderExtensions.containsKey(finder.finderType)) {
- return _finderExtensions[finder.finderType]!.createFinder(finder, this);
+ final String finderType = finder.finderType;
+ if (_finderExtensions.containsKey(finderType)) {
+ return _finderExtensions[finderType]!.createFinder(finder, this);
}
return super.createFinder(finder);
}
- Future<TapResult> _tap(Command command) async {
- final Tap tapCommand = command as Tap;
- final Finder computedFinder = await _waitForElement(
- createFinder(tapCommand.finder).hitTestable()
- );
- await _prober.tap(computedFinder);
- return const TapResult();
- }
-
- Future<WaitForResult> _waitFor(Command command) async {
- final WaitFor waitForCommand = command as WaitFor;
- await _waitForElement(createFinder(waitForCommand.finder));
- return const WaitForResult();
- }
-
- Future<WaitForAbsentResult> _waitForAbsent(Command command) async {
- final WaitForAbsent waitForAbsentCommand = command as WaitForAbsent;
- await _waitForAbsentElement(createFinder(waitForAbsentCommand.finder));
- return const WaitForAbsentResult();
- }
-
- Future<Result?> _waitForCondition(Command command) async {
- assert(command != null);
- final WaitForCondition waitForConditionCommand = command as WaitForCondition;
- final WaitCondition condition = deserializeCondition(waitForConditionCommand.condition);
- await condition.wait();
- return null;
- }
-
- @Deprecated(
- 'This method has been deprecated in favor of _waitForCondition. '
- 'This feature was deprecated after v1.9.3.'
- )
- Future<Result?> _waitUntilNoTransientCallbacks(Command command) async {
- if (SchedulerBinding.instance!.transientCallbackCount != 0)
- await _waitUntilFrame(() => SchedulerBinding.instance!.transientCallbackCount == 0);
- return null;
- }
-
- /// Returns a future that waits until no pending frame is scheduled (frame is synced).
- ///
- /// Specifically, it checks:
- /// * Whether the count of transient callbacks is zero.
- /// * Whether there's no pending request for scheduling a new frame.
- ///
- /// We consider the frame is synced when both conditions are met.
- ///
- /// This method relies on a Flutter Driver mechanism called "frame sync",
- /// which waits for transient animations to finish. Persistent animations will
- /// cause this to wait forever.
- ///
- /// If a test needs to interact with the app while animations are running, it
- /// should avoid this method and instead disable the frame sync using
- /// `set_frame_sync` method. See [FlutterDriver.runUnsynchronized] for more
- /// details on how to do this. Note, disabling frame sync will require the
- /// test author to use some other method to avoid flakiness.
- ///
- /// This method has been deprecated in favor of [_waitForCondition].
- @Deprecated(
- 'This method has been deprecated in favor of _waitForCondition. '
- 'This feature was deprecated after v1.9.3.'
- )
- Future<Result?> _waitUntilNoPendingFrame(Command command) async {
- await _waitUntilFrame(() {
- return SchedulerBinding.instance!.transientCallbackCount == 0
- && !SchedulerBinding.instance!.hasScheduledFrame;
- });
- return null;
- }
-
- Future<GetSemanticsIdResult> _getSemanticsId(Command command) async {
- final GetSemanticsId semanticsCommand = command as GetSemanticsId;
- final Finder target = await _waitForElement(createFinder(semanticsCommand.finder));
- final Iterable<Element> elements = target.evaluate();
- if (elements.length > 1) {
- throw StateError('Found more than one element with the same ID: $elements');
- }
- final Element element = elements.single;
- RenderObject? renderObject = element.renderObject;
- SemanticsNode? node;
- while (renderObject != null && node == null) {
- node = renderObject.debugSemantics;
- renderObject = renderObject.parent as RenderObject?;
- }
- if (node == null)
- throw StateError('No semantics data found');
- return GetSemanticsIdResult(node.id);
- }
-
- Future<GetOffsetResult> _getOffset(Command command) async {
- final GetOffset getOffsetCommand = command as GetOffset;
- final Finder finder = await _waitForElement(createFinder(getOffsetCommand.finder));
- final Element element = finder.evaluate().single;
- final RenderBox box = (element.renderObject as RenderBox?)!;
- Offset localPoint;
- switch (getOffsetCommand.offsetType) {
- case OffsetType.topLeft:
- localPoint = Offset.zero;
- break;
- case OffsetType.topRight:
- localPoint = box.size.topRight(Offset.zero);
- break;
- case OffsetType.bottomLeft:
- localPoint = box.size.bottomLeft(Offset.zero);
- break;
- case OffsetType.bottomRight:
- localPoint = box.size.bottomRight(Offset.zero);
- break;
- case OffsetType.center:
- localPoint = box.size.center(Offset.zero);
- break;
- }
- final Offset globalPoint = box.localToGlobal(localPoint);
- return GetOffsetResult(dx: globalPoint.dx, dy: globalPoint.dy);
- }
-
- Future<DiagnosticsTreeResult> _getDiagnosticsTree(Command command) async {
- final GetDiagnosticsTree diagnosticsCommand = command as GetDiagnosticsTree;
- final Finder finder = await _waitForElement(createFinder(diagnosticsCommand.finder));
- final Element element = finder.evaluate().single;
- DiagnosticsNode diagnosticsNode;
- switch (diagnosticsCommand.diagnosticsType) {
- case DiagnosticsType.renderObject:
- diagnosticsNode = element.renderObject!.toDiagnosticsNode();
- break;
- case DiagnosticsType.widget:
- diagnosticsNode = element.toDiagnosticsNode();
- break;
- }
- return DiagnosticsTreeResult(diagnosticsNode.toJsonMap(DiagnosticsSerializationDelegate(
- subtreeDepth: diagnosticsCommand.subtreeDepth,
- includeProperties: diagnosticsCommand.includeProperties,
- )));
- }
-
- Future<ScrollResult> _scroll(Command command) async {
- final Scroll scrollCommand = command as Scroll;
- final Finder target = await _waitForElement(createFinder(scrollCommand.finder));
- final int totalMoves = scrollCommand.duration.inMicroseconds * scrollCommand.frequency ~/ Duration.microsecondsPerSecond;
- final Offset delta = Offset(scrollCommand.dx, scrollCommand.dy) / totalMoves.toDouble();
- final Duration pause = scrollCommand.duration ~/ totalMoves;
- final Offset startLocation = _prober.getCenter(target);
- Offset currentLocation = startLocation;
- final TestPointer pointer = TestPointer(1);
- _prober.binding.handlePointerEvent(pointer.down(startLocation));
- await Future<void>.value(); // so that down and move don't happen in the same microtask
- for (int moves = 0; moves < totalMoves; moves += 1) {
- currentLocation = currentLocation + delta;
- _prober.binding.handlePointerEvent(pointer.move(currentLocation));
- await Future<void>.delayed(pause);
- }
- _prober.binding.handlePointerEvent(pointer.up());
-
- return const ScrollResult();
- }
-
- Future<ScrollResult> _scrollIntoView(Command command) async {
- final ScrollIntoView scrollIntoViewCommand = command as ScrollIntoView;
- final Finder target = await _waitForElement(createFinder(scrollIntoViewCommand.finder));
- await Scrollable.ensureVisible(target.evaluate().single, duration: const Duration(milliseconds: 100), alignment: scrollIntoViewCommand.alignment);
- return const ScrollResult();
- }
-
- Future<GetTextResult> _getText(Command command) async {
- final GetText getTextCommand = command as GetText;
- final Finder target = await _waitForElement(createFinder(getTextCommand.finder));
-
- final Widget widget = target.evaluate().single.widget;
- String? text;
-
- if (widget.runtimeType == Text) {
- text = (widget as Text).data;
- } else if (widget.runtimeType == RichText) {
- final RichText richText = widget as RichText;
- if (richText.text.runtimeType == TextSpan) {
- text = (richText.text as TextSpan).text;
- }
- } else if (widget.runtimeType == TextField) {
- text = (widget as TextField).controller?.text;
- } else if (widget.runtimeType == TextFormField) {
- text = (widget as TextFormField).controller?.text;
- } else if (widget.runtimeType == EditableText) {
- text = (widget as EditableText).controller.text;
+ @override
+ Command deserializeCommand(Map<String, String> params, DeserializeFinderFactory finderFactory) {
+ final String? kind = params['command'];
+ if(_commandExtensions.containsKey(kind)) {
+ return _commandExtensions[kind]!.deserialize(params, finderFactory, this);
}
- if (text == null) {
- throw UnsupportedError('Type ${widget.runtimeType.toString()} is currently not supported by getText');
+ return super.deserializeCommand(params, finderFactory);
+ }
+
+ @override
+ @protected
+ DataHandler? getDataHandler() {
+ return _requestDataHandler;
+ }
+
+ @override
+ Future<Result?> handleCommand(Command command, WidgetController prober, CreateFinderFactory finderFactory) {
+ final String kind = command.kind;
+ if(_commandExtensions.containsKey(kind)) {
+ return _commandExtensions[kind]!.call(command, prober, finderFactory, this);
}
- return GetTextResult(text);
- }
-
- Future<SetTextEntryEmulationResult> _setTextEntryEmulation(Command command) async {
- final SetTextEntryEmulation setTextEntryEmulationCommand = command as SetTextEntryEmulation;
- if (setTextEntryEmulationCommand.enabled) {
- _testTextInput.register();
- } else {
- _testTextInput.unregister();
- }
- return const SetTextEntryEmulationResult();
- }
-
- Future<EnterTextResult> _enterText(Command command) async {
- if (!_testTextInput.isRegistered) {
- throw 'Unable to fulfill `FlutterDriver.enterText`. Text emulation is '
- 'disabled. You can enable it using `FlutterDriver.setTextEntryEmulation`.';
- }
- final EnterText enterTextCommand = command as EnterText;
- _testTextInput.enterText(enterTextCommand.text);
- return const EnterTextResult();
- }
-
- Future<RequestDataResult> _requestData(Command command) async {
- final RequestData requestDataCommand = command as RequestData;
- return RequestDataResult(_requestDataHandler == null
- ? 'No requestData Extension registered'
- : await _requestDataHandler!(requestDataCommand.message));
- }
-
- Future<SetFrameSyncResult> _setFrameSync(Command command) async {
- final SetFrameSync setFrameSyncCommand = command as SetFrameSync;
- _frameSync = setFrameSyncCommand.enabled;
- return const SetFrameSyncResult();
- }
-
- SemanticsHandle? _semantics;
- bool get _semanticsIsEnabled => RendererBinding.instance!.pipelineOwner.semanticsOwner != null;
-
- Future<SetSemanticsResult> _setSemantics(Command command) async {
- final SetSemantics setSemanticsCommand = command as SetSemantics;
- final bool semanticsWasEnabled = _semanticsIsEnabled;
- if (setSemanticsCommand.enabled && _semantics == null) {
- _semantics = RendererBinding.instance!.pipelineOwner.ensureSemantics();
- if (!semanticsWasEnabled) {
- // wait for the first frame where semantics is enabled.
- final Completer<void> completer = Completer<void>();
- SchedulerBinding.instance!.addPostFrameCallback((Duration d) {
- completer.complete();
- });
- await completer.future;
- }
- } else if (!setSemanticsCommand.enabled && _semantics != null) {
- _semantics!.dispose();
- _semantics = null;
- }
- return SetSemanticsResult(semanticsWasEnabled != _semanticsIsEnabled);
+ return super.handleCommand(command, prober, finderFactory);
}
}
diff --git a/packages/flutter_driver/test/src/real_tests/extension_test.dart b/packages/flutter_driver/test/src/real_tests/extension_test.dart
index 87d4a25..899bc16 100644
--- a/packages/flutter_driver/test/src/real_tests/extension_test.dart
+++ b/packages/flutter_driver/test/src/real_tests/extension_test.dart
@@ -20,6 +20,8 @@
import 'package:flutter_driver/src/extension/extension.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'stubs/stub_command.dart';
+import 'stubs/stub_command_extension.dart';
import 'stubs/stub_finder.dart';
import 'stubs/stub_finder_extension.dart';
@@ -984,6 +986,100 @@
});
});
+ group('extension commands', () {
+ int invokes = 0;
+ final VoidCallback stubCallback = () => invokes++;
+
+ final Widget debugTree = Directionality(
+ textDirection: TextDirection.ltr,
+ child: Center(
+ child: Column(
+ children: <Widget>[
+ TextButton(
+ child: const Text('Whatever'),
+ key: const ValueKey<String>('Button'),
+ onPressed: stubCallback,
+ ),
+ ],
+ ),
+ ),
+ );
+
+ setUp(() {
+ invokes = 0;
+ });
+
+ testWidgets('unknown extension command', (WidgetTester tester) async {
+ final FlutterDriverExtension driverExtension = FlutterDriverExtension(
+ (String arg) async => '',
+ true,
+ commands: <CommandExtension>[],
+ );
+
+ Future<Map<String, dynamic>> invokeCommand(SerializableFinder finder, int times) async {
+ final Map<String, String> arguments = StubNestedCommand(finder, times).serialize();
+ return await driverExtension.call(arguments);
+ }
+
+ await tester.pumpWidget(debugTree);
+
+ final Map<String, dynamic> result = await invokeCommand(ByValueKey('Button'), 10);
+ expect(result['isError'], true);
+ expect(result['response'] is String, true);
+ expect(result['response'] as String, contains('Unsupported command kind StubNestedCommand'));
+ });
+
+ testWidgets('nested command', (WidgetTester tester) async {
+ final FlutterDriverExtension driverExtension = FlutterDriverExtension(
+ (String arg) async => '',
+ true,
+ commands: <CommandExtension>[
+ StubNestedCommandExtension(),
+ ],
+ );
+
+ Future<StubCommandResult> invokeCommand(SerializableFinder finder, int times) async {
+ await driverExtension.call(const SetFrameSync(false).serialize()); // disable frame sync for test to avoid lock
+ final Map<String, String> arguments = StubNestedCommand(finder, times, timeout: const Duration(seconds: 1)).serialize();
+ final Map<String, dynamic> response = await driverExtension.call(arguments);
+ final Map<String, dynamic> commandResponse = response['response'] as Map<String, dynamic>;
+ return StubCommandResult(commandResponse['resultParam'] as String);
+ }
+
+ await tester.pumpWidget(debugTree);
+
+ const int times = 10;
+ final StubCommandResult result = await invokeCommand(ByValueKey('Button'), times);
+ expect(result.resultParam, 'stub response');
+ expect(invokes, times);
+ });
+
+ testWidgets('prober command', (WidgetTester tester) async {
+ final FlutterDriverExtension driverExtension = FlutterDriverExtension(
+ (String arg) async => '',
+ true,
+ commands: <CommandExtension>[
+ StubProberCommandExtension(),
+ ],
+ );
+
+ Future<StubCommandResult> invokeCommand(SerializableFinder finder, int times) async {
+ await driverExtension.call(const SetFrameSync(false).serialize()); // disable frame sync for test to avoid lock
+ final Map<String, String> arguments = StubProberCommand(finder, times, timeout: const Duration(seconds: 1)).serialize();
+ final Map<String, dynamic> response = await driverExtension.call(arguments);
+ final Map<String, dynamic> commandResponse = response['response'] as Map<String, dynamic>;
+ return StubCommandResult(commandResponse['resultParam'] as String);
+ }
+
+ await tester.pumpWidget(debugTree);
+
+ const int times = 10;
+ final StubCommandResult result = await invokeCommand(ByValueKey('Button'), times);
+ expect(result.resultParam, 'stub response');
+ expect(invokes, times);
+ });
+ });
+
group('waitUntilFrameSync', () {
FlutterDriverExtension driverExtension;
Map<String, dynamic> result;
diff --git a/packages/flutter_driver/test/src/real_tests/find_test.dart b/packages/flutter_driver/test/src/real_tests/find_test.dart
index df41576..fccf5b5 100644
--- a/packages/flutter_driver/test/src/real_tests/find_test.dart
+++ b/packages/flutter_driver/test/src/real_tests/find_test.dart
@@ -4,6 +4,7 @@
// @dart = 2.8
+import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_driver/src/common/find.dart';
import 'package:mockito/mockito.dart';
diff --git a/packages/flutter_driver/test/src/real_tests/stubs/stub_command.dart b/packages/flutter_driver/test/src/real_tests/stubs/stub_command.dart
new file mode 100644
index 0000000..779bd15
--- /dev/null
+++ b/packages/flutter_driver/test/src/real_tests/stubs/stub_command.dart
@@ -0,0 +1,58 @@
+// 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 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_driver/flutter_driver.dart';
+
+class StubNestedCommand extends CommandWithTarget {
+ StubNestedCommand(SerializableFinder finder, this.times, {Duration? timeout})
+ : super(finder, timeout: timeout);
+
+ StubNestedCommand.deserialize(
+ Map<String, String> json, DeserializeFinderFactory finderFactory)
+ : times = int.parse(json['times']!),
+ super.deserialize(json, finderFactory);
+
+ @override
+ Map<String, String> serialize() {
+ return super.serialize()..addAll(<String, String>{'times': '$times'});
+ }
+
+ @override
+ String get kind => 'StubNestedCommand';
+
+ final int times;
+}
+
+class StubProberCommand extends CommandWithTarget {
+ StubProberCommand(SerializableFinder finder, this.times, {Duration? timeout})
+ : super(finder, timeout: timeout);
+
+ StubProberCommand.deserialize(Map<String, String> json, DeserializeFinderFactory finderFactory)
+ : times = int.parse(json['times']!),
+ super.deserialize(json, finderFactory);
+
+ @override
+ Map<String, String> serialize() {
+ return super.serialize()..addAll(<String, String>{'times': '$times'});
+ }
+
+ @override
+ String get kind => 'StubProberCommand';
+
+ final int times;
+}
+
+class StubCommandResult extends Result {
+ const StubCommandResult(this.resultParam);
+
+ final String resultParam;
+
+ @override
+ Map<String, dynamic> toJson() {
+ return <String, dynamic>{
+ 'resultParam': resultParam,
+ };
+ }
+}
diff --git a/packages/flutter_driver/test/src/real_tests/stubs/stub_command_extension.dart b/packages/flutter_driver/test/src/real_tests/stubs/stub_command_extension.dart
new file mode 100644
index 0000000..20d9e64
--- /dev/null
+++ b/packages/flutter_driver/test/src/real_tests/stubs/stub_command_extension.dart
@@ -0,0 +1,51 @@
+// 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 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_driver/flutter_driver.dart';
+import 'package:flutter_driver/src/common/message.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'stub_command.dart';
+
+class StubNestedCommandExtension extends CommandExtension {
+ @override
+ String get commandKind => 'StubNestedCommand';
+
+ @override
+ Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+ final StubNestedCommand stubCommand = command as StubNestedCommand;
+ handlerFactory.waitForElement(finderFactory.createFinder(stubCommand.finder));
+ for (int index = 0; index < stubCommand.times; index++) {
+ await handlerFactory.handleCommand(Tap(stubCommand.finder), prober, finderFactory);
+ }
+ return const StubCommandResult('stub response');
+ }
+
+ @override
+ Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) {
+ return StubNestedCommand.deserialize(params, finderFactory);
+ }
+}
+
+class StubProberCommandExtension extends CommandExtension {
+ @override
+ String get commandKind => 'StubProberCommand';
+
+ @override
+ Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
+ final StubProberCommand stubCommand = command as StubProberCommand;
+ final Finder finder = finderFactory.createFinder(stubCommand.finder);
+ handlerFactory.waitForElement(finder);
+ for (int index = 0; index < stubCommand.times; index++) {
+ await prober.tap(finder);
+ }
+ return const StubCommandResult('stub response');
+ }
+
+ @override
+ Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) {
+ return StubProberCommand.deserialize(params, finderFactory);
+ }
+}
diff --git a/packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart b/packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart
index 6b5694e..b24e554 100644
--- a/packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart
+++ b/packages/flutter_driver/test/src/real_tests/stubs/stub_finder_extension.dart
@@ -3,9 +3,9 @@
// found in the LICENSE file.
import 'package:flutter/src/widgets/framework.dart';
-import 'package:flutter_driver/driver_extension.dart';
-import 'package:flutter_driver/src/common/create_finder_factory.dart';
import 'package:flutter_test/src/finders.dart';
+import 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_driver/src/common/handler_factory.dart';
import 'package:flutter_driver/src/common/find.dart';
import 'stub_finder.dart';
diff --git a/packages/flutter_test/lib/src/_matchers_io.dart b/packages/flutter_test/lib/src/_matchers_io.dart
index 9c1f022..80d6919 100644
--- a/packages/flutter_test/lib/src/_matchers_io.dart
+++ b/packages/flutter_test/lib/src/_matchers_io.dart
@@ -49,27 +49,31 @@
@override
Future<String?> matchAsync(dynamic item) async {
- Future<ui.Image> imageFuture;
- if (item is Future<ui.Image>) {
+ Future<ui.Image?> imageFuture;
+ if (item is Future<ui.Image?>) {
imageFuture = item;
} else if (item is ui.Image) {
imageFuture = Future<ui.Image>.value(item);
- } else {
- final Finder finder = item as Finder;
- final Iterable<Element> elements = finder.evaluate();
+ } else if (item is Finder) {
+ final Iterable<Element> elements = item.evaluate();
if (elements.isEmpty) {
return 'could not be rendered because no widget was found';
} else if (elements.length > 1) {
return 'matched too many widgets';
}
imageFuture = captureImage(elements.single);
+ } else {
+ throw 'must provide a Finder, Image, or Future<Image>';
}
final Uri testNameUri = goldenFileComparator.getTestUri(key, version);
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding;
return binding.runAsync<String?>(() async {
- final ui.Image image = await imageFuture;
+ final ui.Image? image = await imageFuture;
+ if (image == null) {
+ throw 'Future<Image> completed to null';
+ }
final ByteData? bytes = await image.toByteData(format: ui.ImageByteFormat.png);
if (bytes == null)
return 'could not encode screenshot.';
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index 0b76a0b..116746a 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -1011,17 +1011,19 @@
addTime(additionalTime);
- return realAsyncZone.run<Future<T>>(() {
+ return realAsyncZone.run<Future<T?>>(() async {
_pendingAsyncTasks = Completer<void>();
- return callback().catchError((Object exception, StackTrace stack) {
+ T? result;
+ try {
+ result = await callback();
+ } catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'Flutter test framework',
context: ErrorDescription('while running async test code'),
));
- return null;
- }).whenComplete(() {
+ } finally {
// We complete the _pendingAsyncTasks future successfully regardless of
// whether an exception occurred because in the case of an exception,
// we already reported the exception to FlutterError. Moreover,
@@ -1029,7 +1031,8 @@
// exception due to zone error boundaries.
_pendingAsyncTasks!.complete();
_pendingAsyncTasks = null;
- });
+ }
+ return result;
});
}
diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart
index 63c35a7..b0b2c96 100644
--- a/packages/flutter_test/lib/src/controller.dart
+++ b/packages/flutter_test/lib/src/controller.dart
@@ -841,15 +841,18 @@
/// key press. To simulate individual down and/or up events, see
/// [sendKeyDownEvent] and [sendKeyUpEvent].
///
+ /// Returns true if the key down event was handled by the framework.
+ ///
/// See also:
///
/// - [sendKeyDownEvent] to simulate only a key down event.
/// - [sendKeyUpEvent] to simulate only a key up event.
- Future<void> sendKeyEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
+ Future<bool> sendKeyEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
assert(platform != null);
- await simulateKeyDownEvent(key, platform: platform);
+ final bool handled = await simulateKeyDownEvent(key, platform: platform);
// Internally wrapped in async guard.
- return simulateKeyUpEvent(key, platform: platform);
+ await simulateKeyUpEvent(key, platform: platform);
+ return handled;
}
/// Simulates sending a physical key down event through the system channel.
@@ -864,11 +867,13 @@
///
/// Keys that are down when the test completes are cleared after each test.
///
+ /// Returns true if the key event was handled by the framework.
+ ///
/// See also:
///
/// - [sendKeyUpEvent] to simulate the corresponding key up event.
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
- Future<void> sendKeyDownEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
+ Future<bool> sendKeyDownEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
assert(platform != null);
// Internally wrapped in async guard.
return simulateKeyDownEvent(key, platform: platform);
@@ -883,11 +888,13 @@
/// [Platform.operatingSystem] to make the event appear to be from that type
/// of system. Defaults to "android". May not be null.
///
+ /// Returns true if the key event was handled by the framework.
+ ///
/// See also:
///
/// - [sendKeyDownEvent] to simulate the corresponding key down event.
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
- Future<void> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
+ Future<bool> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = 'android' }) async {
assert(platform != null);
// Internally wrapped in async guard.
return simulateKeyUpEvent(key, platform: platform);
diff --git a/packages/flutter_test/lib/src/event_simulation.dart b/packages/flutter_test/lib/src/event_simulation.dart
index dc51a72..d633e3b 100644
--- a/packages/flutter_test/lib/src/event_simulation.dart
+++ b/packages/flutter_test/lib/src/event_simulation.dart
@@ -516,20 +516,32 @@
///
/// Keys that are down when the test completes are cleared after each test.
///
+ /// Returns true if the key event was handled by the framework.
+ ///
/// See also:
///
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
- static Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
- return TestAsyncUtils.guard<void>(() async {
+ static Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
+ return await TestAsyncUtils.guard<bool>(() async {
platform ??= Platform.operatingSystem;
assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation');
final Map<String, dynamic> data = getKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey);
+ bool result = false;
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
- (ByteData? data) { },
+ (ByteData? data) {
+ if (data == null) {
+ return;
+ }
+ final Map<String, dynamic> decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
+ if (decoded['handled'] as bool) {
+ result = true;
+ }
+ }
);
+ return result;
});
}
@@ -543,20 +555,32 @@
/// system. Defaults to the operating system that the test is running on. Some
/// platforms (e.g. Windows, iOS) are not yet supported.
///
+ /// Returns true if the key event was handled by the framework.
+ ///
/// See also:
///
/// - [simulateKeyDownEvent] to simulate the corresponding key down event.
- static Future<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
- return TestAsyncUtils.guard<void>(() async {
+ static Future<bool> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
+ return TestAsyncUtils.guard<bool>(() async {
platform ??= Platform.operatingSystem;
assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation');
final Map<String, dynamic> data = getKeyData(key, platform: platform!, isDown: false, physicalKey: physicalKey);
+ bool result = false;
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
- (ByteData? data) { },
+ (ByteData? data) {
+ if (data == null) {
+ return;
+ }
+ final Map<String, dynamic> decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
+ if (decoded['handled'] as bool) {
+ result = true;
+ }
+ }
);
+ return result;
});
}
}
@@ -576,10 +600,12 @@
///
/// Keys that are down when the test completes are cleared after each test.
///
+/// Returns true if the key event was handled by the framework.
+///
/// See also:
///
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
-Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
+Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey);
}
@@ -596,9 +622,11 @@
/// system. Defaults to the operating system that the test is running on. Some
/// platforms (e.g. Windows, iOS) are not yet supported.
///
+/// Returns true if the key event was handled by the framework.
+///
/// See also:
///
/// - [simulateKeyDownEvent] to simulate the corresponding key down event.
-Future<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
+Future<bool> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
return KeyEventSimulator.simulateKeyUpEvent(key, platform: platform, physicalKey: physicalKey);
}
diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart
index 46a15b2..0a4f912 100644
--- a/packages/flutter_test/lib/src/matchers.dart
+++ b/packages/flutter_test/lib/src/matchers.dart
@@ -468,6 +468,7 @@
bool hasToggledState = false,
bool isToggled = false,
bool hasImplicitScrolling = false,
+ bool isSlider = false,
// Actions //
bool hasTapAction = false,
bool hasLongPressAction = false,
@@ -519,6 +520,7 @@
if (hasToggledState) SemanticsFlag.hasToggledState,
if (isToggled) SemanticsFlag.isToggled,
if (hasImplicitScrolling) SemanticsFlag.hasImplicitScrolling,
+ if (isSlider) SemanticsFlag.isSlider
];
final List<SemanticsAction> actions = <SemanticsAction>[
diff --git a/packages/flutter_test/test/test_config/config_test_utils.dart b/packages/flutter_test/test/test_config/config_test_utils.dart
index 6c17239..d86cf7b 100644
--- a/packages/flutter_test/test/test_config/config_test_utils.dart
+++ b/packages/flutter_test/test/test_config/config_test_utils.dart
@@ -11,7 +11,7 @@
String? expectedStringValue, {
Map<Type, dynamic> otherExpectedValues = const <Type, dynamic>{int: isNull},
}) {
- final String actualStringValue = Zone.current[String] as String;
+ final String? actualStringValue = Zone.current[String] as String?;
final Map<Type, dynamic> otherActualValues = otherExpectedValues.map<Type, dynamic>(
(Type key, dynamic value) {
return MapEntry<Type, dynamic>(key, Zone.current[key]);
diff --git a/packages/flutter_tools/bin/tool_backend.dart b/packages/flutter_tools/bin/tool_backend.dart
index 9df4aa0..678d10a 100644
--- a/packages/flutter_tools/bin/tool_backend.dart
+++ b/packages/flutter_tools/bin/tool_backend.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// @dart = 2.9
+
// Do not add package imports to this file.
import 'dart:convert'; // ignore: dart_convert_import.
import 'dart:io'; // ignore: dart_io_import.
diff --git a/packages/flutter_tools/build.yaml b/packages/flutter_tools/build.yaml
deleted file mode 100644
index c2d33c80..0000000
--- a/packages/flutter_tools/build.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-targets:
- $default:
- builders:
- build_web_compilers|entrypoint:
- enabled: false
- sources:
- exclude:
- - "test/data/**"
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 9645a4e..b1846b6 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -279,6 +279,9 @@
} else {
command.add('-q');
}
+ if (!buildInfo.androidGradleDaemon) {
+ command.add('--no-daemon');
+ }
if (globals.artifacts is LocalEngineArtifacts) {
final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts;
final Directory localEngineRepo = _getLocalEngineRepo(
@@ -592,6 +595,9 @@
} else {
command.add('-q');
}
+ if (!buildInfo.androidGradleDaemon) {
+ command.add('--no-daemon');
+ }
if (target != null && target.isNotEmpty) {
command.add('-Ptarget=$target');
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 17d9b9d..16927d3 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -34,6 +34,7 @@
this.packagesPath = '.packages',
this.nullSafetyMode = NullSafetyMode.autodetect,
this.codeSizeDirectory,
+ this.androidGradleDaemon = true,
});
final BuildMode mode;
@@ -118,6 +119,20 @@
/// will be written for code size profiling.
final String codeSizeDirectory;
+ /// Whether to enable the Gradle daemon when performing an Android build.
+ ///
+ /// Starting the daemon is the default behavior of the gradle wrapper script created
+ /// in a Flutter project. Setting this value to false will cause the tool to pass
+ /// `--no-daemon` to the gradle wrapper script, preventing it from spawning a daemon
+ /// process.
+ ///
+ /// For one-off builds or CI systems, preventing the daemon from spawning will
+ /// reduce system resource usage, at the cost of any subsequent builds starting
+ /// up slightly slower.
+ ///
+ /// The Gradle daemon may also be disabled in the Android application's properties file.
+ final bool androidGradleDaemon;
+
static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart
index 5824a73..1f256e5 100644
--- a/packages/flutter_tools/lib/src/commands/build_aar.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aar.dart
@@ -43,6 +43,7 @@
usesTrackWidgetCreation(verboseHelp: false);
addNullSafetyModeOptions(hide: !verboseHelp);
addEnableExperimentation(hide: !verboseHelp);
+ addAndroidSpecificBuildOptions(hide: !verboseHelp);
argParser
..addMultiOption(
'target-platform',
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 7cdee98..750fc15 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -33,6 +33,7 @@
addBuildPerformanceFile(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
usesAnalyzeSizeFlag();
+ addAndroidSpecificBuildOptions(hide: !verboseHelp);
argParser
..addFlag('split-per-abi',
negatable: false,
diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart
index c84ef66..97c1ccf 100644
--- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart
+++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart
@@ -33,6 +33,7 @@
addNullSafetyModeOptions(hide: !verboseHelp);
addEnableExperimentation(hide: !verboseHelp);
usesAnalyzeSizeFlag();
+ addAndroidSpecificBuildOptions(hide: !verboseHelp);
argParser.addMultiOption('target-platform',
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index b474a07..362aea9 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -546,7 +546,10 @@
generateSyntheticPackage: false,
);
final FlutterProject project = FlutterProject.fromDirectory(directory);
- await project.ensureReadyForPlatformSpecificTooling(checkProjects: false);
+ await project.ensureReadyForPlatformSpecificTooling(
+ androidPlatform: true,
+ iosPlatform: true,
+ );
}
return generatedCount;
}
@@ -641,7 +644,6 @@
}
}
-
final FlutterProject project = FlutterProject.fromDirectory(directory);
final bool generateAndroid = templateContext['android'] == true;
if (generateAndroid) {
@@ -680,7 +682,15 @@
offline: boolArg('offline'),
generateSyntheticPackage: false,
);
- await project.ensureReadyForPlatformSpecificTooling(checkProjects: pluginExampleApp);
+
+ await project.ensureReadyForPlatformSpecificTooling(
+ androidPlatform: templateContext['android'] as bool ?? false,
+ iosPlatform: templateContext['ios'] as bool ?? false,
+ linuxPlatform: templateContext['linux'] as bool ?? false,
+ macOSPlatform: templateContext['macos'] as bool ?? false,
+ windowsPlatform: templateContext['windows'] as bool ?? false,
+ webPlatform: templateContext['web'] as bool ?? false,
+ );
}
if (templateContext['android'] == true) {
gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index 0d59fcb..d966bac 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -246,7 +246,19 @@
webUri = residentRunner.uri;
}
- final LaunchResult result = await appStarter(this, webUri, package, applicationBinary != null);
+ // Attempt to launch the application up to 3 times, to validate whether it
+ // is possible to reduce flakiness by hardnening the launch code.
+ int attempt = 0;
+ LaunchResult result;
+ while (attempt < 3) {
+ // On attempts past 1, assume the application is built correctly and re-use it.
+ result = await appStarter(this, webUri, package, applicationBinary != null || attempt > 0);
+ if (result != null) {
+ break;
+ }
+ attempt += 1;
+ globals.printError('Application failed to start on attempt: $attempt');
+ }
if (result == null) {
throwToolExit('Application failed to start. Will not run test. Quitting.', exitCode: 1);
}
@@ -374,6 +386,8 @@
globals.printStatus('Stopping application instance.');
await appStopper(this, package);
}
+
+ await device?.dispose();
}
return FlutterCommandResult.success();
diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart
index d31b337..583233d 100644
--- a/packages/flutter_tools/lib/src/commands/packages.dart
+++ b/packages/flutter_tools/lib/src/commands/packages.dart
@@ -147,13 +147,13 @@
final FlutterProject rootProject = FlutterProject.fromPath(target);
await _runPubGet(target, rootProject);
- await rootProject.ensureReadyForPlatformSpecificTooling(checkProjects: true);
+ await rootProject.regeneratePlatformSpecificTooling();
// Get/upgrade packages in example app as well
if (rootProject.hasExampleApp) {
final FlutterProject exampleProject = rootProject.example;
await _runPubGet(exampleProject.directory.path, exampleProject);
- await exampleProject.ensureReadyForPlatformSpecificTooling(checkProjects: true);
+ await exampleProject.regeneratePlatformSpecificTooling();
}
return FlutterCommandResult.success();
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 34ae7f0..fb0a27c 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -88,6 +88,7 @@
usesDeviceUserOption();
usesDeviceTimeoutOption();
addDdsOptions(verboseHelp: verboseHelp);
+ addAndroidSpecificBuildOptions(hide: !verboseHelp);
}
bool get traceStartup => boolArg('trace-startup');
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 4058e67..62a177d 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -600,7 +600,7 @@
message = fileUri.toString();
} else {
message = request.packageConfig.toPackageUri(fileUri)?.toString() ??
- toMultiRootPath(request.mainUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);
+ toMultiRootPath(fileUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);
}
_server.stdin.writeln(message);
_logger.printTrace(message.toString());
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index fd7f604..4f8e62d 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -296,16 +296,11 @@
_osUtils,
);
await request.addStream(contents);
- // The contents has already been streamed, closing the request should
- // not take long but we are experiencing hangs with it, see #63869.
- //
// Once the bug in Dart is solved we can remove the timeout
- // (https://github.com/dart-lang/sdk/issues/43525). The timeout was
- // chosen to be inflated based on the max observed time when running the
- // tests in "Google Tests".
+ // (https://github.com/dart-lang/sdk/issues/43525).
try {
final HttpClientResponse response = await request.close().timeout(
- const Duration(milliseconds: 10000));
+ const Duration(seconds: 60));
response.listen((_) {},
onError: (dynamic error) {
_logger.printTrace('error: $error');
diff --git a/packages/flutter_tools/lib/src/intellij/intellij.dart b/packages/flutter_tools/lib/src/intellij/intellij.dart
index cf8b576..f63b132 100644
--- a/packages/flutter_tools/lib/src/intellij/intellij.dart
+++ b/packages/flutter_tools/lib/src/intellij/intellij.dart
@@ -15,6 +15,25 @@
/// This searches on the provided plugin path for a JAR archive, then
/// unzips it to parse the META-INF/plugin.xml for version information.
///
+/// In particular, the Intellij Flutter plugin has a different structure depending on the version.
+///
+/// For flutter-intellij plugin v49 or lower:
+/// flutter-intellij/lib/flutter-intellij.jar ( includes META-INF/plugin.xml )
+///
+/// For flutter-intellij plugin v50 or higher and for Intellij 2020.2:
+/// flutter-intellij/lib/flutter-intellij-X.Y.Z.jar
+/// flutter-idea-X.Y.Z.jar ( includes META-INF/plugin.xml )
+/// flutter-studio-X.Y.Z.jar
+///
+/// For flutter-intellij plugin v50 or higher and for Intellij 2020.3:
+/// flutter-intellij/lib/flutter-intellij-X.Y.Z.jar
+/// flutter-idea-X.Y.Z.jar ( includes META-INF/plugin.xml )
+///
+/// where X.Y.Z is the version number.
+///
+/// Intellij Flutter plugin's files can be found here:
+/// https://plugins.jetbrains.com/plugin/9212-flutter/versions/stable
+///
/// See also:
/// * [IntellijValidator], the validator base class that uses this to check
/// plugin versions.
@@ -69,17 +88,61 @@
return _fileSystem.isDirectorySync(packagePath);
}
- String _readPackageVersion(String packageName) {
- final String jarPath = packageName.endsWith('.jar')
- ? _fileSystem.path.join(pluginsPath, packageName)
- : _fileSystem.path.join(pluginsPath, packageName, 'lib', '$packageName.jar');
- final File file = _fileSystem.file(jarPath);
- if (!file.existsSync()) {
- return null;
+ ArchiveFile _findPluginXml(String packageName) {
+ final List<File> mainJarFileList = <File>[];
+ if (packageName.endsWith('.jar')) {
+ // package exists (checked in _hasPackage)
+ mainJarFileList.add(_fileSystem.file(_fileSystem.path.join(pluginsPath, packageName)));
+ } else {
+ final String packageLibPath =
+ _fileSystem.path.join(pluginsPath, packageName, 'lib');
+ if (!_fileSystem.isDirectorySync(packageLibPath)) {
+ return null;
+ }
+ // Collect the files with a file suffix of .jar/.zip that contains the plugin.xml file
+ final List<File> pluginJarFiles = _fileSystem
+ .directory(_fileSystem.path.join(pluginsPath, packageName, 'lib'))
+ .listSync()
+ .whereType<File>()
+ .where((File file) {
+ final String fileExt= _fileSystem.path.extension(file.path);
+ return fileExt == '.jar' || fileExt == '.zip';
+ })
+ .toList();
+
+ if (pluginJarFiles.isEmpty) {
+ return null;
+ }
+ // Prefer file with the same suffix as the package name
+ pluginJarFiles.sort((File a, File b) {
+ final bool aStartWithPackageName =
+ a.basename.toLowerCase().startsWith(packageName.toLowerCase());
+ final bool bStartWithPackageName =
+ b.basename.toLowerCase().startsWith(packageName.toLowerCase());
+ if (bStartWithPackageName != aStartWithPackageName) {
+ return bStartWithPackageName ? 1 : -1;
+ }
+ return a.basename.length - b.basename.length;
+ });
+ mainJarFileList.addAll(pluginJarFiles);
}
- try {
+
+ for (final File file in mainJarFileList) {
final Archive archive = ZipDecoder().decodeBytes(file.readAsBytesSync());
final ArchiveFile archiveFile = archive.findFile('META-INF/plugin.xml');
+ if (archiveFile != null) {
+ return archiveFile;
+ }
+ }
+ return null;
+ }
+
+ String _readPackageVersion(String packageName) {
+ try {
+ final ArchiveFile archiveFile = _findPluginXml(packageName);
+ if (archiveFile == null) {
+ return null;
+ }
final String content = utf8.decode(archiveFile.content as List<int>);
const String versionStartTag = '<version>';
final int start = content.indexOf(versionStartTag);
diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
index 77d8e87..0dd390f 100644
--- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart
+++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
@@ -9,6 +9,7 @@
import 'package:dwds/dwds.dart';
import 'package:html/dom.dart';
import 'package:html/parser.dart';
+import 'package:logging/logging.dart' as logging;
import 'package:meta/meta.dart';
import 'package:mime/mime.dart' as mime;
import 'package:package_config/package_config.dart';
@@ -226,6 +227,10 @@
};
};
+ logging.Logger.root.onRecord.listen((logging.LogRecord event) {
+ globals.printTrace('${event.loggerName}: ${event.message}');
+ });
+
// In debug builds, spin up DWDS and the full asset server.
final Dwds dwds = await dwdsLauncher(
assetReader: server,
@@ -300,6 +305,11 @@
// handle requests for JavaScript source, dart sources maps, or asset files.
@visibleForTesting
Future<shelf.Response> handleRequest(shelf.Request request) async {
+ if (request.method != 'GET') {
+ // Assets are served via GET only.
+ return shelf.Response.notFound('');
+ }
+
final String requestPath = _stripBasePath(request.url.path, basePath);
if (requestPath == null) {
@@ -976,6 +986,11 @@
];
Future<shelf.Response> handle(shelf.Request request) async {
+ if (request.method != 'GET') {
+ // Assets are served via GET only.
+ return shelf.Response.notFound('');
+ }
+
Uri fileUri;
final String requestPath = _stripBasePath(request.url.path, basePath);
diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
index f76d70e..2205adc 100644
--- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
@@ -657,7 +657,7 @@
final bool hasWebPlugins = (await findPlugins(flutterProject))
.any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, webPlatform: true);
final Uri generatedUri = globals.fs.currentDirectory
.childDirectory('lib')
diff --git a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
index c84e503..c329280 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart
@@ -18,7 +18,7 @@
) async {
final FlutterProject project = xcodeProject.parent;
// Ensure that the plugin list is up to date, since hasPlugins relies on it.
- await refreshPluginsList(project);
+ await refreshPluginsList(project, macOSPlatform: project.macos.existsSync());
if (!(hasPlugins(project) || (project.isModule && xcodeProject.podfile.existsSync()))) {
return;
}
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index be7df41..3d5292b 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -1145,11 +1145,12 @@
/// Rewrites the `.flutter-plugins` file of [project] based on the plugin
/// dependencies declared in `pubspec.yaml`.
///
-/// If `checkProjects` is true, then plugins are only injected into directories
-/// which already exist.
-///
/// Assumes `pub get` has been executed since last change to `pubspec.yaml`.
-Future<void> refreshPluginsList(FlutterProject project, {bool checkProjects = false}) async {
+Future<void> refreshPluginsList(
+ FlutterProject project, {
+ bool iosPlatform = false,
+ bool macOSPlatform = false,
+}) async {
final List<Plugin> plugins = await findPlugins(project);
// TODO(franciscojma): Remove once migration is complete.
@@ -1159,12 +1160,10 @@
final bool changed = _writeFlutterPluginsList(project, plugins);
if (changed || legacyChanged) {
createPluginSymlinks(project, force: true);
- if (!checkProjects || project.ios.existsSync()) {
+ if (iosPlatform) {
globals.cocoaPods.invalidatePodInstallOutput(project.ios);
}
- // TODO(stuartmorgan): Potentially add checkProjects once a decision has
- // made about how to handle macOS in existing projects.
- if (project.macos.existsSync()) {
+ if (macOSPlatform) {
globals.cocoaPods.invalidatePodInstallOutput(project.macos);
}
}
@@ -1172,34 +1171,40 @@
/// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
///
-/// If `checkProjects` is true, then plugins are only injected into directories
-/// which already exist.
-///
/// Assumes [refreshPluginsList] has been called since last change to `pubspec.yaml`.
-Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false}) async {
+Future<void> injectPlugins(
+ FlutterProject project, {
+ bool androidPlatform = false,
+ bool iosPlatform = false,
+ bool linuxPlatform = false,
+ bool macOSPlatform = false,
+ bool windowsPlatform = false,
+ bool webPlatform = false,
+}) async {
final List<Plugin> plugins = await findPlugins(project);
// Sort the plugins by name to keep ordering stable in generated files.
plugins.sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
- if ((checkProjects && project.android.existsSync()) || !checkProjects) {
+ if (androidPlatform) {
await _writeAndroidPluginRegistrant(project, plugins);
}
- if ((checkProjects && project.ios.existsSync()) || !checkProjects) {
+ if (iosPlatform) {
await _writeIOSPluginRegistrant(project, plugins);
}
- // TODO(stuartmorgan): Revisit the conditions here once the plans for handling
- // desktop in existing projects are in place. For now, ignore checkProjects
- // on desktop and always treat it as true.
- if (featureFlags.isLinuxEnabled && project.linux.existsSync()) {
+ if (linuxPlatform) {
await _writeLinuxPluginFiles(project, plugins);
}
- if (featureFlags.isMacOSEnabled && project.macos.existsSync()) {
+ if (macOSPlatform) {
await _writeMacOSPluginRegistrant(project, plugins);
}
- if (featureFlags.isWindowsEnabled && project.windows.existsSync()) {
+ if (windowsPlatform) {
await _writeWindowsPluginFiles(project, plugins);
}
- for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) {
- if (!project.isModule && (!checkProjects || subproject.existsSync())) {
+ if (!project.isModule) {
+ final List<XcodeBasedProject> darwinProjects = <XcodeBasedProject>[
+ if (iosPlatform) project.ios,
+ if (macOSPlatform) project.macos,
+ ];
+ for (final XcodeBasedProject subproject in darwinProjects) {
if (plugins.isNotEmpty) {
await globals.cocoaPods.setupPodfile(subproject);
}
@@ -1210,7 +1215,7 @@
}
}
}
- if (featureFlags.isWebEnabled && project.web.existsSync()) {
+ if (webPlatform) {
await _writeWebPluginRegistrant(project, plugins);
}
}
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 4607337..f2dd9ac 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -229,38 +229,64 @@
return manifest;
}
- /// Generates project files necessary to make Gradle builds work on Android
- /// and CocoaPods+Xcode work on iOS, for app and module projects only.
- // TODO(cyanglaz): The param `checkProjects` is confusing. We should give it a better name
- // or add some documentation explaining what it does, or both.
- // https://github.com/flutter/flutter/issues/60023
- Future<void> ensureReadyForPlatformSpecificTooling({bool checkProjects = false}) async {
+ /// Reapplies template files and regenerates project files and plugin
+ /// registrants for app and module projects only.
+ ///
+ /// Will not create project platform directories if they do not already exist.
+ Future<void> regeneratePlatformSpecificTooling() async {
+ return ensureReadyForPlatformSpecificTooling(
+ androidPlatform: android.existsSync(),
+ iosPlatform: ios.existsSync(),
+ // TODO(stuartmorgan): Revisit the conditions here once the plans for handling
+ // desktop in existing projects are in place.
+ linuxPlatform: featureFlags.isLinuxEnabled && linux.existsSync(),
+ macOSPlatform: featureFlags.isMacOSEnabled && macos.existsSync(),
+ windowsPlatform: featureFlags.isWindowsEnabled && windows.existsSync(),
+ webPlatform: featureFlags.isWebEnabled && web.existsSync(),
+ );
+ }
+
+ /// Applies template files and generates project files and plugin
+ /// registrants for app and module projects only for the specified platforms.
+ Future<void> ensureReadyForPlatformSpecificTooling({
+ bool androidPlatform = false,
+ bool iosPlatform = false,
+ bool linuxPlatform = false,
+ bool macOSPlatform = false,
+ bool windowsPlatform = false,
+ bool webPlatform = false,
+ }) async {
if (!directory.existsSync() || hasExampleApp) {
return;
}
- await refreshPluginsList(this);
- if ((android.existsSync() && checkProjects) || !checkProjects) {
+ await refreshPluginsList(this, iosPlatform: iosPlatform, macOSPlatform: macOSPlatform);
+ if (androidPlatform) {
await android.ensureReadyForPlatformSpecificTooling();
}
- if ((ios.existsSync() && checkProjects) || !checkProjects) {
+ if (iosPlatform) {
await ios.ensureReadyForPlatformSpecificTooling();
}
- // TODO(stuartmorgan): Revisit conditions once there is a plan for handling
- // non-default platform projects. For now, always treat checkProjects as
- // true for desktop.
- if (featureFlags.isLinuxEnabled && linux.existsSync()) {
+ if (linuxPlatform) {
await linux.ensureReadyForPlatformSpecificTooling();
}
- if (featureFlags.isMacOSEnabled && macos.existsSync()) {
+ if (macOSPlatform) {
await macos.ensureReadyForPlatformSpecificTooling();
}
- if (featureFlags.isWindowsEnabled && windows.existsSync()) {
+ if (windowsPlatform) {
await windows.ensureReadyForPlatformSpecificTooling();
}
- if (featureFlags.isWebEnabled && web.existsSync()) {
+ if (webPlatform) {
await web.ensureReadyForPlatformSpecificTooling();
}
- await injectPlugins(this, checkProjects: checkProjects);
+ await injectPlugins(
+ this,
+ androidPlatform: androidPlatform,
+ iosPlatform: iosPlatform,
+ linuxPlatform: linuxPlatform,
+ macOSPlatform: macOSPlatform,
+ windowsPlatform: windowsPlatform,
+ webPlatform: webPlatform,
+ );
}
/// Returns a json encoded string containing the [appName], [version], and [buildNumber] that is used to generate version.json
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 7cfbf06..e654bf4 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -114,6 +114,7 @@
static const String kDeviceTimeout = 'device-timeout';
static const String kAnalyzeSize = 'analyze-size';
static const String kNullAssertions = 'null-assertions';
+ static const String kAndroidGradleDaemon = 'android-gradle-daemon';
}
abstract class FlutterCommand extends Command<void> {
@@ -611,6 +612,18 @@
);
}
+ void addAndroidSpecificBuildOptions({ bool hide = false }) {
+ argParser.addFlag(
+ FlutterOptions.kAndroidGradleDaemon,
+ help: 'Whether to enable the Gradle daemon when performing an Android build. '
+ 'Starting the daemon is the default behavior of the gradle wrapper script created '
+ ' in a Flutter project. Setting this flag to false corresponds to passing '
+ "'--no-daemon' to the gradle wrapper script. This flag will cause the daemon "
+ 'process to terminate after the build is completed',
+ defaultsTo: true,
+ );
+ }
+
/// Adds build options common to all of the desktop build commands.
void addCommonDesktopBuildOptions({ bool verboseHelp = false }) {
addBuildModeFlags(verboseHelp: verboseHelp);
@@ -764,6 +777,9 @@
? stringArg(FlutterOptions.kSplitDebugInfoOption)
: null;
+ final bool androidGradleDaemon = !argParser.options.containsKey(FlutterOptions.kAndroidGradleDaemon)
+ || boolArg(FlutterOptions.kAndroidGradleDaemon);
+
if (dartObfuscation && (splitDebugInfoPath == null || splitDebugInfoPath.isEmpty)) {
throwToolExit(
'"--${FlutterOptions.kDartObfuscationOption}" can only be used in '
@@ -827,6 +843,7 @@
packagesPath: globalResults['packages'] as String ?? '.packages',
nullSafetyMode: nullSafetyMode,
codeSizeDirectory: codeSizeDirectory,
+ androidGradleDaemon: androidGradleDaemon,
);
}
@@ -996,7 +1013,7 @@
);
// All done updating dependencies. Release the cache lock.
Cache.releaseLock();
- await project.ensureReadyForPlatformSpecificTooling(checkProjects: true);
+ await project.regeneratePlatformSpecificTooling();
} else {
Cache.releaseLock();
}
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index e9d2175..d494c57 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -41,7 +41,7 @@
outputDirectory.deleteSync(recursive: true);
outputDirectory.createSync(recursive: true);
}
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, webPlatform: true);
final Status status = globals.logger.startProgress('Compiling $target for the Web...');
final Stopwatch sw = Stopwatch()..start();
try {
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml
index c2b90fc..d5baa02 100644
--- a/packages/flutter_tools/pubspec.yaml
+++ b/packages/flutter_tools/pubspec.yaml
@@ -113,4 +113,10 @@
# Exclude this package from the hosted API docs.
nodoc: true
-# PUBSPEC CHECKSUM: 138b
+# PUBSPEC CHECKSUM: 9d86
+
+dependency_overrides:
+ dwds:
+ git:
+ url: git@github.com:dart-lang/webdev.git
+ path: dwds
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/main.cc b/packages/flutter_tools/templates/app/linux.tmpl/main.cc
index 058e617..e7c5c54 100644
--- a/packages/flutter_tools/templates/app/linux.tmpl/main.cc
+++ b/packages/flutter_tools/templates/app/linux.tmpl/main.cc
@@ -1,10 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
- // Only X11 is currently supported.
- // Wayland support is being developed: https://github.com/flutter/flutter/issues/57932.
- gdk_set_allowed_backends("x11");
-
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/my_application.cc.tmpl b/packages/flutter_tools/templates/app/linux.tmpl/my_application.cc.tmpl
index 5097955..fe188bb 100644
--- a/packages/flutter_tools/templates/app/linux.tmpl/my_application.cc.tmpl
+++ b/packages/flutter_tools/templates/app/linux.tmpl/my_application.cc.tmpl
@@ -1,6 +1,9 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
#include "flutter/generated_plugin_registrant.h"
@@ -14,11 +17,35 @@
static void my_application_activate(GApplication* application) {
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
- GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
- gtk_widget_show(GTK_WIDGET(header_bar));
- gtk_header_bar_set_title(header_bar, "{{projectName}}");
- gtk_header_bar_set_show_close_button(header_bar, TRUE);
- gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+
+ // Use a header bar when running in GNOME as this is the common style used
+ // by applications and is the setup most users will be using (e.g. Ubuntu
+ // desktop).
+ // If running on X and not using GNOME then just use a traditional title bar
+ // in case the window manager does more exotic layout, e.g. tiling.
+ // If running on Wayland assume the header bar will work (may need changing
+ // if future cases occur).
+ gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+ GdkScreen *screen = gtk_window_get_screen(window);
+ if (GDK_IS_X11_SCREEN(screen)) {
+ const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+ if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+ use_header_bar = FALSE;
+ }
+ }
+#endif
+ if (use_header_bar) {
+ GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_show(GTK_WIDGET(header_bar));
+ gtk_header_bar_set_title(header_bar, "{{projectName}}");
+ gtk_header_bar_set_show_close_button(header_bar, TRUE);
+ gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+ }
+ else {
+ gtk_window_set_title(window, "{{projectName}}");
+ }
+
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
diff --git a/packages/flutter_tools/templates/package/pubspec.yaml.tmpl b/packages/flutter_tools/templates/package/pubspec.yaml.tmpl
index 69a6beb..4943d54 100644
--- a/packages/flutter_tools/templates/package/pubspec.yaml.tmpl
+++ b/packages/flutter_tools/templates/package/pubspec.yaml.tmpl
@@ -6,7 +6,7 @@
environment:
sdk: ">=2.7.0 <3.0.0"
- flutter: ">=1.17.0 <2.0.0"
+ flutter: ">=1.17.0"
dependencies:
flutter:
diff --git a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
index c2f83ec..4ce3f0d 100644
--- a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
+++ b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
@@ -6,7 +6,7 @@
environment:
sdk: ">=2.7.0 <3.0.0"
- flutter: ">=1.20.0 <2.0.0"
+ flutter: ">=1.20.0"
dependencies:
flutter:
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
index 0ed3aa0..9538ada 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
@@ -99,7 +99,7 @@
testUsingContext('returns 1 when app fails to run', () async {
testDeviceManager.addDevice(MockDevice());
- appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async => null);
+ appStarter = expectAsync4((DriveCommand command, Uri webUri, ApplicationPackage package, bool prebuiltApplication) async => null, count: 3);
final String testApp = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e.dart');
final String testFile = globals.fs.path.join(tempDir.path, 'test_driver', 'e2e_test.dart');
@@ -228,6 +228,7 @@
'10',
];
await createTestCommandRunner(command).run(args);
+ verify(mockDevice.dispose());
expect(testLogger.errorText, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
@@ -529,6 +530,7 @@
prebuiltApplication: false,
userIdentifier: anyNamed('userIdentifier'),
));
+ verify(mockDevice.dispose());
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
@@ -558,6 +560,7 @@
prebuiltApplication: false,
userIdentifier: anyNamed('userIdentifier'),
));
+ verify(mockDevice.dispose());
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
index 97b2ff0..e046d40 100755
--- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -7,6 +7,7 @@
import 'dart:typed_data';
import 'package:args/command_runner.dart';
+import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
@@ -662,6 +663,52 @@
expect(testLogger.statusText, isNot(contains('https://flutter.dev/go/android-project-migration')));
});
+ testUsingContext('app does not include desktop or web by default', () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(<String>['create', '--no-pub', projectDir.path]);
+
+ expect(projectDir.childDirectory('linux'), isNot(exists));
+ expect(projectDir.childDirectory('macos'), isNot(exists));
+ expect(projectDir.childDirectory('windows'), isNot(exists));
+ expect(projectDir.childDirectory('web'), isNot(exists));
+ }, overrides: <Type, Generator>{
+ FeatureFlags: () => TestFeatureFlags(),
+ });
+
+ testUsingContext('plugin does not include desktop or web by default',
+ () async {
+ Cache.flutterRoot = '../..';
+ when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
+ when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
+
+ final CreateCommand command = CreateCommand();
+ final CommandRunner<void> runner = createTestCommandRunner(command);
+
+ await runner.run(
+ <String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+
+ expect(projectDir.childDirectory('linux'), isNot(exists));
+ expect(projectDir.childDirectory('macos'), isNot(exists));
+ expect(projectDir.childDirectory('windows'), isNot(exists));
+ expect(projectDir.childDirectory('web'), isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('linux'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('macos'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('windows'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('web'),
+ isNot(exists));
+ }, overrides: <Type, Generator>{
+ FeatureFlags: () => TestFeatureFlags(),
+ });
+
testUsingContext('app supports Linux if requested', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
@@ -670,28 +717,24 @@
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
- await runner.run(<String>['create', '--no-pub', projectDir.path]);
+ await runner.run(<String>[
+ 'create',
+ '--no-pub',
+ '--platforms=linux',
+ projectDir.path,
+ ]);
- expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), true);
+ expect(
+ projectDir.childDirectory('linux').childFile('CMakeLists.txt'), exists);
+ expect(projectDir.childDirectory('android'), isNot(exists));
+ expect(projectDir.childDirectory('ios'), isNot(exists));
+ expect(projectDir.childDirectory('windows'), isNot(exists));
+ expect(projectDir.childDirectory('macos'), isNot(exists));
+ expect(projectDir.childDirectory('web'), isNot(exists));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
- testUsingContext('app does not include Linux by default', () async {
- Cache.flutterRoot = '../..';
- when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
- when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
-
- final CreateCommand command = CreateCommand();
- final CommandRunner<void> runner = createTestCommandRunner(command);
-
- await runner.run(<String>['create', '--no-pub', projectDir.path]);
-
- expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), false);
- }, overrides: <Type, Generator>{
- FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
- });
-
testUsingContext('plugin supports Linux if requested', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
@@ -702,17 +745,32 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=linux', projectDir.path]);
- expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), true);
- validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
- 'linux',
- ], pluginClass: 'FlutterProjectPlugin',
+ expect(
+ projectDir.childDirectory('linux').childFile('CMakeLists.txt'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('linux'), exists);
+ expect(projectDir.childDirectory('example').childDirectory('android'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('ios'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('windows'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('macos'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('web'),
+ isNot(exists));
+ validatePubspecForPlugin(
+ projectDir: projectDir.absolute.path,
+ expectedPlatforms: const <String>[
+ 'linux',
+ ],
+ pluginClass: 'FlutterProjectPlugin',
unexpectedPlatforms: <String>['some_platform']);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
- testUsingContext('plugin does not include Linux by default', () async {
+ testUsingContext('app supports macOS if requested', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
@@ -720,27 +778,23 @@
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
- await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+ await runner.run(<String>[
+ 'create',
+ '--no-pub',
+ '--platforms=macos',
+ projectDir.path,
+ ]);
- expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), false);
+ expect(
+ projectDir.childDirectory('macos').childDirectory('Runner.xcworkspace'),
+ exists);
+ expect(projectDir.childDirectory('android'), isNot(exists));
+ expect(projectDir.childDirectory('ios'), isNot(exists));
+ expect(projectDir.childDirectory('linux'), isNot(exists));
+ expect(projectDir.childDirectory('windows'), isNot(exists));
+ expect(projectDir.childDirectory('web'), isNot(exists));
}, overrides: <Type, Generator>{
- FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
- });
-
- testUsingContext('app does not include macOS by default', () async {
- Cache.flutterRoot = '../..';
- when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
- when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
-
- final CreateCommand command = CreateCommand();
- final CommandRunner<void> runner = createTestCommandRunner(command);
-
- await runner.run(<String>['create', '--no-pub', projectDir.path]);
-
- expect(projectDir.childDirectory('macos').childDirectory('Runner.xcworkspace').existsSync(), false);
- }, overrides: <Type, Generator>{
- FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false),
+ FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
testUsingContext('plugin supports macOS if requested', () async {
@@ -753,8 +807,20 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]);
- expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true);
+ expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec'),
+ exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('macos'), exists);
+ expect(projectDir.childDirectory('example').childDirectory('linux'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('android'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('ios'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('windows'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('web'),
+ isNot(exists));
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'macos',
], pluginClass: 'FlutterProjectPlugin',
@@ -763,22 +829,6 @@
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
- testUsingContext('plugin does not include macOS by default', () async {
- Cache.flutterRoot = '../..';
- when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
- when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
-
- final CreateCommand command = CreateCommand();
- final CommandRunner<void> runner = createTestCommandRunner(command);
-
- await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
-
- expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), false);
- }, overrides: <Type, Generator>{
- FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false),
- });
-
testUsingContext('app supports Windows if requested', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
@@ -787,9 +837,20 @@
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
- await runner.run(<String>['create', '--no-pub', projectDir.path]);
+ await runner.run(<String>[
+ 'create',
+ '--no-pub',
+ '--platforms=windows',
+ projectDir.path,
+ ]);
- expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), true);
+ expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt'),
+ exists);
+ expect(projectDir.childDirectory('android'), isNot(exists));
+ expect(projectDir.childDirectory('ios'), isNot(exists));
+ expect(projectDir.childDirectory('linux'), isNot(exists));
+ expect(projectDir.childDirectory('macos'), isNot(exists));
+ expect(projectDir.childDirectory('web'), isNot(exists));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
@@ -805,7 +866,7 @@
await runner.run(<String>['create', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
final File resourceFile = projectDir.childDirectory('windows').childDirectory('runner').childFile('Runner.rc');
- expect(resourceFile.existsSync(), true);
+ expect(resourceFile, exists);
final String contents = resourceFile.readAsStringSync();
expect(contents, contains('"CompanyName", "com.foo.bar"'));
expect(contents, contains('"ProductName", "flutter_project"'));
@@ -813,21 +874,6 @@
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
- testUsingContext('app does not include Windows by default', () async {
- Cache.flutterRoot = '../..';
- when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
- when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
-
- final CreateCommand command = CreateCommand();
- final CommandRunner<void> runner = createTestCommandRunner(command);
-
- await runner.run(<String>['create', '--no-pub', projectDir.path]);
-
- expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), false);
- }, overrides: <Type, Generator>{
- FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
- });
-
testUsingContext('plugin supports Windows if requested', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
@@ -838,8 +884,31 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]);
- expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true);
+ expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt'),
+ exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('windows'), exists);
+ expect(
+ projectDir
+ .childDirectory('example')
+ .childDirectory('android'),
+ isNot(exists));
+ expect(
+ projectDir.childDirectory('example').childDirectory('ios'),
+ isNot(exists));
+ expect(
+ projectDir
+ .childDirectory('example')
+ .childDirectory('linux'),
+ isNot(exists));
+ expect(
+ projectDir
+ .childDirectory('example')
+ .childDirectory('macos'),
+ isNot(exists));
+ expect(
+ projectDir.childDirectory('example').childDirectory('web'),
+ isNot(exists));
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'windows'
], pluginClass: 'FlutterProjectPlugin',
@@ -848,7 +917,7 @@
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
- testUsingContext('plugin does not include Windows by default', () async {
+ testUsingContext('app supports web if requested', () async {
Cache.flutterRoot = '../..';
when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision);
when(mockFlutterVersion.channel).thenReturn(frameworkChannel);
@@ -856,12 +925,23 @@
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
- await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
+ await runner.run(<String>[
+ 'create',
+ '--no-pub',
+ '--platforms=web',
+ projectDir.path,
+ ]);
- expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false);
+ expect(
+ projectDir.childDirectory('web').childFile('index.html'),
+ exists);
+ expect(projectDir.childDirectory('android'), isNot(exists));
+ expect(projectDir.childDirectory('ios'), isNot(exists));
+ expect(projectDir.childDirectory('linux'), isNot(exists));
+ expect(projectDir.childDirectory('macos'), isNot(exists));
+ expect(projectDir.childDirectory('windows'), isNot(exists));
}, overrides: <Type, Generator>{
- FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
+ FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
});
testUsingContext('plugin uses new platform schema', () async {
@@ -1516,7 +1596,7 @@
await runner.run(args);
final File expectedFile = globals.fs.file(outputFile);
- expect(expectedFile.existsSync(), isTrue);
+ expect(expectedFile, exists);
expect(expectedFile.readAsStringSync(), equals(samplesIndexJson));
}, overrides: <Type, Generator>{
HttpClientFactory: () =>
@@ -1555,7 +1635,7 @@
];
await expectLater(runner.run(args), throwsToolExit(exitCode: 2, message: 'Failed to write samples'));
- expect(globals.fs.file(outputFile).existsSync(), isFalse);
+ expect(globals.fs.file(outputFile), isNot(exists));
}, overrides: <Type, Generator>{
HttpClientFactory: () =>
() => MockHttpClient(404, result: 'not found'),
@@ -1573,21 +1653,27 @@
// TODO(cyanglaz): no-op iOS folder should be removed after 1.20.0 release
// https://github.com/flutter/flutter/issues/59787
- expect(projectDir.childDirectory('ios').existsSync(), false);
- expect(projectDir.childDirectory('android').existsSync(), false);
- expect(projectDir.childDirectory('web').existsSync(), false);
- expect(projectDir.childDirectory('linux').existsSync(), false);
- expect(projectDir.childDirectory('windows').existsSync(), false);
- expect(projectDir.childDirectory('macos').existsSync(), false);
+ expect(projectDir.childDirectory('ios'), isNot(exists));
+ expect(projectDir.childDirectory('android'), isNot(exists));
+ expect(projectDir.childDirectory('web'), isNot(exists));
+ expect(projectDir.childDirectory('linux'), isNot(exists));
+ expect(projectDir.childDirectory('windows'), isNot(exists));
+ expect(projectDir.childDirectory('macos'), isNot(exists));
// TODO(cyanglaz): no-op iOS folder should be removed after 1.20.0 release
// https://github.com/flutter/flutter/issues/59787
- expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('web').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), false);
+ expect(projectDir.childDirectory('example').childDirectory('ios'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('android'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('web'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('linux'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('windows'),
+ isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('macos'),
+ isNot(exists));
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: <String>[
'some_platform'
], pluginClass: 'somePluginClass',
@@ -1607,8 +1693,8 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]);
- expect(projectDir.childDirectory('ios').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+ expect(projectDir.childDirectory('ios'), exists);
+ expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: <String>[
'ios',
], pluginClass: 'FlutterProjectPlugin',
@@ -1627,8 +1713,9 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
- expect(projectDir.childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
+ expect(projectDir.childDirectory('android'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('android'), exists);
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'android'
], pluginClass: 'FlutterProjectPlugin',
@@ -1647,7 +1734,9 @@
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]);
- expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), true);
+ expect(
+ projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
+ exists);
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'web'
], pluginClass: 'FlutterProjectWeb',
@@ -1667,7 +1756,9 @@
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]);
- expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), false);
+ expect(
+ projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
+ isNot(exists));
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'some_platform'
], pluginClass: 'somePluginClass',
@@ -1686,8 +1777,8 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]);
- expect(projectDir.childDirectory('ios').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+ expect(projectDir.childDirectory('ios'), exists);
+ expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
});
@@ -1702,8 +1793,9 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
- expect(projectDir.childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
+ expect(projectDir.childDirectory('android'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('android'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
});
@@ -1718,8 +1810,9 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=linux', projectDir.path]);
- expect(projectDir.childDirectory('linux').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), true);
+ expect(projectDir.childDirectory('linux'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('linux'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
@@ -1734,8 +1827,9 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]);
- expect(projectDir.childDirectory('macos').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true);
+ expect(projectDir.childDirectory('macos'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('macos'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
@@ -1750,8 +1844,9 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]);
- expect(projectDir.childDirectory('windows').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true);
+ expect(projectDir.childDirectory('windows'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('windows'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
@@ -1766,7 +1861,9 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]);
- expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), true);
+ expect(
+ projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
+ exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
});
@@ -1779,18 +1876,19 @@
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]);
- expect(projectDir.childDirectory('ios').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+ expect(projectDir.childDirectory('ios'), exists);
+ expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'ios',
], pluginClass: 'FlutterProjectPlugin',
unexpectedPlatforms: <String>['some_platform']);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]);
- expect(projectDir.childDirectory('macos').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true);
- expect(projectDir.childDirectory('ios').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+ expect(projectDir.childDirectory('macos'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('macos'), exists);
+ expect(projectDir.childDirectory('ios'), exists);
+ expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
@@ -1803,13 +1901,14 @@
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios,android', projectDir.path]);
- expect(projectDir.childDirectory('ios').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+ expect(projectDir.childDirectory('ios'), exists);
+ expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
- expect(projectDir.childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('ios').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true);
+ expect(projectDir.childDirectory('android'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('android'), exists);
+ expect(projectDir.childDirectory('ios'), exists);
+ expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'ios', 'android'
], pluginClass: 'FlutterProjectPlugin',
@@ -1849,18 +1948,21 @@
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
- expect(projectDir.childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
+ expect(projectDir.childDirectory('android'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('android'), exists);
globals.fs.file(globals.fs.path.join(projectDir.path, 'android')).deleteSync(recursive: true);
globals.fs.file(globals.fs.path.join(projectDir.path, 'example/android')).deleteSync(recursive: true);
- expect(projectDir.childDirectory('android').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), false);
+ expect(projectDir.childDirectory('android'), isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('android'),
+ isNot(exists));
await runner.run(<String>['create', '--no-pub', projectDir.path]);
- expect(projectDir.childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
+ expect(projectDir.childDirectory('android'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('android'), exists);
});
testUsingContext('create a plugin with android, delete then re-create folders while also adding windows', () async {
@@ -1871,20 +1973,24 @@
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
- expect(projectDir.childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
+ expect(projectDir.childDirectory('android'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('android'), exists);
globals.fs.file(globals.fs.path.join(projectDir.path, 'android')).deleteSync(recursive: true);
globals.fs.file(globals.fs.path.join(projectDir.path, 'example/android')).deleteSync(recursive: true);
- expect(projectDir.childDirectory('android').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), false);
+ expect(projectDir.childDirectory('android'), isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('android'),
+ isNot(exists));
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]);
- expect(projectDir.childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true);
- expect(projectDir.childDirectory('windows').existsSync(), true);
- expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true);
+ expect(projectDir.childDirectory('android'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('android'), exists);
+ expect(projectDir.childDirectory('windows'), exists);
+ expect(
+ projectDir.childDirectory('example').childDirectory('windows'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
@@ -1899,8 +2005,8 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
- expect(projectDir.childDirectory('android').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), false);
+ expect(projectDir.childDirectory('android'), isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('android'), isNot(exists));
});
testUsingContext('flutter create . on and existing plugin does not add windows folder even feature is enabled', () async {
@@ -1913,8 +2019,8 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
- expect(projectDir.childDirectory('windows').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false);
+ expect(projectDir.childDirectory('windows'), isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('windows'), isNot(exists));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
@@ -1929,8 +2035,8 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
- expect(projectDir.childDirectory('linux').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), false);
+ expect(projectDir.childDirectory('linux'), isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('linux'), isNot(exists));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
@@ -1945,7 +2051,7 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
- expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), false);
+ expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart'), isNot(exists));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
});
@@ -1960,8 +2066,8 @@
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]);
await runner.run(<String>['create', '--no-pub', projectDir.path]);
- expect(projectDir.childDirectory('macos').existsSync(), false);
- expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), false);
+ expect(projectDir.childDirectory('macos'), isNot(exists));
+ expect(projectDir.childDirectory('example').childDirectory('macos'), isNot(exists));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
@@ -2048,8 +2154,8 @@
.childFile(headerName);
final File implFile = platformDir.childFile('$classFilenameBase.cc');
// Ensure that the files have the right names.
- expect(headerFile.existsSync(), true);
- expect(implFile.existsSync(), true);
+ expect(headerFile, exists);
+ expect(implFile, exists);
// Ensure that the include is correct.
expect(implFile.readAsStringSync(), contains(headerName));
// Ensure that the CMake file has the right target and source values.
@@ -2081,8 +2187,8 @@
.childFile(headerName);
final File implFile = platformDir.childFile('$classFilenameBase.cpp');
// Ensure that the files have the right names.
- expect(headerFile.existsSync(), true);
- expect(implFile.existsSync(), true);
+ expect(headerFile, exists);
+ expect(implFile, exists);
// Ensure that the include is correct.
expect(implFile.readAsStringSync(), contains(headerName));
// Ensure that the plugin target name matches the post-processed version.
@@ -2116,8 +2222,8 @@
.childFile(headerName);
final File implFile = platformDir.childFile('$classFilenameBase.cc');
// Ensure that the files have the right names.
- expect(headerFile.existsSync(), true);
- expect(implFile.existsSync(), true);
+ expect(headerFile, exists);
+ expect(implFile, exists);
// Ensure that the include is correct.
expect(implFile.readAsStringSync(), contains(headerName));
// Ensure that the CMake file has the right target and source values.
@@ -2154,8 +2260,8 @@
.childFile(headerName);
final File implFile = platformDir.childFile('$classFilenameBase.cpp');
// Ensure that the files have the right names.
- expect(headerFile.existsSync(), true);
- expect(implFile.existsSync(), true);
+ expect(headerFile, exists);
+ expect(implFile, exists);
// Ensure that the include is correct.
expect(implFile.readAsStringSync(), contains(headerName));
// Ensure that the CMake file has the right target and source values.
diff --git a/packages/flutter_tools/test/general.shard/android/gradle_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_test.dart
index 0ad0805..be389df 100644
--- a/packages/flutter_tools/test/general.shard/android/gradle_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/gradle_test.dart
@@ -1328,7 +1328,7 @@
ProcessManager: () => mockProcessManager,
});
- testUsingContext('logs success event after a sucessful retry', () async {
+ testUsingContext('logs success event after a successful retry', () async {
int testFnCalled = 0;
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
@@ -1415,7 +1415,7 @@
Usage: () => mockUsage,
});
- testUsingContext('performs code size analyis and sends analytics', () async {
+ testUsingContext('performs code size analysis and sends analytics', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
@@ -2059,6 +2059,59 @@
ProcessManager: () => mockProcessManager,
});
+ testUsingContext('honors --no-android-gradle-daemon setting', () async {
+ (globals.processManager as FakeProcessManager).addCommand(
+ const FakeCommand(command: <String>[
+ '/android/gradlew',
+ '-q',
+ '--no-daemon',
+ '-Ptarget-platform=android-arm,android-arm64,android-x64',
+ '-Ptarget=lib/main.dart',
+ '-Ptrack-widget-creation=false',
+ 'assembleRelease'
+ ],
+ ));
+ fileSystem.file('android/gradlew').createSync(recursive: true);
+
+ fileSystem.directory('android')
+ .childFile('gradle.properties')
+ .createSync(recursive: true);
+
+ fileSystem.file('android/build.gradle')
+ .createSync(recursive: true);
+
+ fileSystem.directory('android')
+ .childDirectory('app')
+ .childFile('build.gradle')
+ ..createSync(recursive: true)
+ ..writeAsStringSync('apply from: irrelevant/flutter.gradle');
+
+ await expectLater(() async {
+ await buildGradleApp(
+ project: FlutterProject.current(),
+ androidBuildInfo: const AndroidBuildInfo(
+ BuildInfo(
+ BuildMode.release,
+ null,
+ treeShakeIcons: false,
+ androidGradleDaemon: false,
+ ),
+ ),
+ target: 'lib/main.dart',
+ isBuildingBundle: false,
+ localGradleErrors: const <GradleHandledError>[],
+ );
+ }, throwsToolExit());
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ AndroidStudio: () => mockAndroidStudio,
+ Artifacts: () => Artifacts.test(),
+ Cache: () => cache,
+ Platform: () => android,
+ FileSystem: () => fileSystem,
+ ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
+ });
+
testUsingContext('build aar uses selected local engine,the engine abi is arm', () async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.android_arm, mode: anyNamed('mode'))).thenReturn('engine');
diff --git a/packages/flutter_tools/test/general.shard/compile_incremental_test.dart b/packages/flutter_tools/test/general.shard/compile_incremental_test.dart
index 4652cfb..48f12eb 100644
--- a/packages/flutter_tools/test/general.shard/compile_incremental_test.dart
+++ b/packages/flutter_tools/test/general.shard/compile_incremental_test.dart
@@ -199,14 +199,70 @@
await _recompile(streamController, generatorWithScheme, mockFrontendServerStdIn,
'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0\n',
mainUri: Uri.parse('file:///foo/bar/fizz/main.dart'),
- expectedUri: 'scheme:///main.dart');
+ expectedMainUri: 'scheme:///main.dart');
await _accept(streamController, generatorWithScheme, mockFrontendServerStdIn, r'^accept\n$');
await _recompile(streamController, generatorWithScheme, mockFrontendServerStdIn,
'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0\n',
mainUri: Uri.parse('file:///foo/bar/fizz/main.dart'),
- expectedUri: 'scheme:///main.dart');
+ expectedMainUri: 'scheme:///main.dart');
+ // No sources returned from reject command.
+ await _reject(streamController, generatorWithScheme, mockFrontendServerStdIn, 'result abc\nabc\n',
+ r'^reject\n$');
+ verifyNoMoreInteractions(mockFrontendServerStdIn);
+ expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
+ expect(testLogger.errorText, equals(
+ 'line0\nline1\n'
+ 'line1\nline2\n'
+ 'line1\nline2\n'
+ ));
+ });
+
+ testWithoutContext('incremental compile and recompile non-entrypoint file with filesystem scheme', () async {
+ final Uri mainUri = Uri.parse('file:///foo/bar/fizz/main.dart');
+ const String expectedMainUri = 'scheme:///main.dart';
+ final List<Uri> updatedUris = <Uri>[
+ mainUri,
+ Uri.parse('file:///foo/bar/fizz/other.dart'),
+ ];
+ const List<String> expectedUpdatedUris = <String>[
+ expectedMainUri,
+ 'scheme:///other.dart',
+ ];
+
+ final StreamController<List<int>> streamController = StreamController<List<int>>();
+ when(mockFrontendServer.stdout)
+ .thenAnswer((Invocation invocation) => streamController.stream);
+ streamController.add(utf8.encode('result abc\nline0\nline1\nabc\nabc /path/to/main.dart.dill 0\n'));
+ await generatorWithScheme.recompile(
+ Uri.parse('file:///foo/bar/fizz/main.dart'),
+ null, /* invalidatedFiles */
+ outputPath: '/build/',
+ packageConfig: PackageConfig.empty,
+ );
+ expect(mockFrontendServerStdIn.getAndClear(), 'compile scheme:///main.dart\n');
+
+ // No accept or reject commands should be issued until we
+ // send recompile request.
+ await _accept(streamController, generatorWithScheme, mockFrontendServerStdIn, '');
+ await _reject(streamController, generatorWithScheme, mockFrontendServerStdIn, '', '');
+
+ await _recompile(streamController, generatorWithScheme, mockFrontendServerStdIn,
+ 'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0\n',
+ mainUri: mainUri,
+ expectedMainUri: expectedMainUri,
+ updatedUris: updatedUris,
+ expectedUpdatedUris: expectedUpdatedUris);
+
+ await _accept(streamController, generatorWithScheme, mockFrontendServerStdIn, r'^accept\n$');
+
+ await _recompile(streamController, generatorWithScheme, mockFrontendServerStdIn,
+ 'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0\n',
+ mainUri: mainUri,
+ expectedMainUri: expectedMainUri,
+ updatedUris: updatedUris,
+ expectedUpdatedUris: expectedUpdatedUris);
// No sources returned from reject command.
await _reject(streamController, generatorWithScheme, mockFrontendServerStdIn, 'result abc\nabc\n',
r'^reject\n$');
@@ -291,9 +347,13 @@
String mockCompilerOutput, {
bool suppressErrors = false,
Uri mainUri,
- String expectedUri = '/path/to/main.dart',
+ String expectedMainUri = '/path/to/main.dart',
+ List<Uri> updatedUris,
+ List<String> expectedUpdatedUris,
}) async {
mainUri ??= Uri.parse('/path/to/main.dart');
+ updatedUris ??= <Uri>[mainUri];
+ expectedUpdatedUris ??= <String>[expectedMainUri];
// Put content into the output stream after generator.recompile gets
// going few lines below, resets completer.
@@ -302,7 +362,7 @@
});
final CompilerOutput output = await generator.recompile(
mainUri,
- <Uri>[mainUri],
+ updatedUris,
outputPath: '/build/',
packageConfig: PackageConfig.empty,
suppressErrors: suppressErrors,
@@ -313,8 +373,11 @@
final List<String> parts = commands.split(whitespace);
// Test that uuid matches at beginning and end.
- expect(parts[2], equals(parts[4]));
- expect(parts[1], equals(expectedUri));
+ expect(parts[2], equals(parts[3 + updatedUris.length]));
+ expect(parts[1], equals(expectedMainUri));
+ for (int i = 0; i < expectedUpdatedUris.length; i++) {
+ expect(parts[3 + i], equals(expectedUpdatedUris[i]));
+ }
mockFrontendServerStdIn.stdInWrites.clear();
}
diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart
index 8b15864..5a7667d 100644
--- a/packages/flutter_tools/test/general.shard/devfs_test.dart
+++ b/packages/flutter_tools/test/general.shard/devfs_test.dart
@@ -93,7 +93,7 @@
expect(content.isModified, isFalse);
});
- testWithoutContext('DevFS retries uploads when connection resert by peer', () async {
+ testWithoutContext('DevFS retries uploads when connection reset by peer', () async {
final HttpClient httpClient = MockHttpClient();
final FileSystem fileSystem = MemoryFileSystem.test();
final OperatingSystemUtils osUtils = MockOperatingSystemUtils();
@@ -356,7 +356,7 @@
expect(writer.written, true);
});
- testWithoutContext('Local DevFSwriter can copy and write files', () async {
+ testWithoutContext('Local DevFSWriter can copy and write files', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final File file = fileSystem.file('foo_bar')
..writeAsStringSync('goodbye');
@@ -373,7 +373,7 @@
expect(fileSystem.file('/foo/bar/devfs/goodbye').readAsStringSync(), 'goodbye');
});
- testWithoutContext('Local DevFSwriter turns FileSystemException into DevFSException', () async {
+ testWithoutContext('Local DevFSWriter turns FileSystemException into DevFSException', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final LocalDevFSWriter writer = LocalDevFSWriter(fileSystem: fileSystem);
final File file = MockFile();
@@ -383,84 +383,6 @@
Uri.parse('goodbye'): DevFSFileContent(file),
}, Uri.parse('/foo/bar/devfs/')), throwsA(isA<DevFSException>()));
});
-
- testWithoutContext('test handles request closure hangs', () async {
- final FileSystem fileSystem = MemoryFileSystem.test();
- final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
- requests: <VmServiceExpectation>[createDevFSRequest],
- );
- final HttpClient httpClient = MockHttpClient();
- final MockHttpClientRequest httpRequest = MockHttpClientRequest();
- when(httpRequest.headers).thenReturn(MockHttpHeaders());
- when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) {
- return Future<HttpClientRequest>.value(httpRequest);
- });
- int closeCount = 0;
- final Completer<MockHttpClientResponse> hanger = Completer<MockHttpClientResponse>();
- final Completer<MockHttpClientResponse> succeeder = Completer<MockHttpClientResponse>();
- final List<Completer<MockHttpClientResponse>> closeCompleters =
- <Completer<MockHttpClientResponse>>[hanger, succeeder];
- succeeder.complete(MockHttpClientResponse());
-
- when(httpRequest.close()).thenAnswer((Invocation invocation) {
- final Completer<MockHttpClientResponse> completer = closeCompleters[closeCount];
- closeCount += 1;
- return completer.future;
- });
- when(httpRequest.abort()).thenAnswer((_) {
- hanger.completeError(const HttpException('aborted'));
- });
- when(httpRequest.done).thenAnswer((_) {
- if (closeCount == 1) {
- return hanger.future;
- } else if (closeCount == 2) {
- return succeeder.future;
- } else {
- // This branch shouldn't happen.
- fail('This branch should not happen');
- }
- });
-
- final BufferLogger logger = BufferLogger.test();
- final DevFS devFS = DevFS(
- fakeVmServiceHost.vmService,
- 'test',
- fileSystem.currentDirectory,
- fileSystem: fileSystem,
- logger: logger,
- osUtils: FakeOperatingSystemUtils(),
- httpClient: httpClient,
- );
-
- await devFS.create();
- final DateTime previousCompile = devFS.lastCompiled;
-
- final MockResidentCompiler residentCompiler = MockResidentCompiler();
- when(residentCompiler.recompile(
- any,
- any,
- outputPath: anyNamed('outputPath'),
- packageConfig: anyNamed('packageConfig'),
- )).thenAnswer((Invocation invocation) async {
- fileSystem.file('example').createSync();
- return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]);
- });
-
- final UpdateFSReport report = await devFS.update(
- mainUri: Uri.parse('lib/main.dart'),
- generator: residentCompiler,
- dillOutputPath: 'lib/foo.dill',
- pathToReload: 'lib/foo.txt.dill',
- trackWidgetCreation: false,
- invalidatedFiles: <Uri>[],
- packageConfig: PackageConfig.empty,
- );
-
- expect(report.success, true);
- expect(devFS.lastCompiled, isNot(previousCompile));
- expect(closeCount, 2);
- expect(logger.errorText, '');
- });
}
class MockHttpClientRequest extends Mock implements HttpClientRequest {}
diff --git a/packages/flutter_tools/test/general.shard/intellij/intellij_test.dart b/packages/flutter_tools/test/general.shard/intellij/intellij_test.dart
index f76ce97..d50ee41 100644
--- a/packages/flutter_tools/test/general.shard/intellij/intellij_test.dart
+++ b/packages/flutter_tools/test/general.shard/intellij/intellij_test.dart
@@ -69,6 +69,81 @@
expect(message.message, contains('recommended minimum version'));
});
+ testWithoutContext('IntelliJPlugins can read the package version of the flutter-intellij 50.0+/IntelliJ 2020.2+ layout', () async {
+ final IntelliJPlugins plugins = IntelliJPlugins(_kPluginsPath, fileSystem: fileSystem);
+
+ final Archive flutterIdeaJarArchive = buildSingleFileArchive('META-INF/plugin.xml', r'''
+<idea-plugin version="2">
+<name>Flutter</name>
+<version>50.0</version>
+</idea-plugin>
+''');
+ writeFileCreatingDirectories(
+ fileSystem.path.join(_kPluginsPath, 'flutter-intellij', 'lib', 'flutter-idea-50.0.jar'),
+ ZipEncoder().encode(flutterIdeaJarArchive),
+ );
+ final Archive flutterIntellijJarArchive = buildSingleFileArchive('META-INF/MANIFEST.MF', r'''
+Manifest-Version: 1.0
+''');
+ writeFileCreatingDirectories(
+ fileSystem.path.join(_kPluginsPath, 'flutter-intellij', 'lib', 'flutter-intellij-50.0.jar'),
+ ZipEncoder().encode(flutterIntellijJarArchive),
+ );
+
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ plugins.validatePackage(messages,
+ <String>[
+ 'flutter-intellij',
+ 'flutter-intellij.jar',
+ ],
+ 'Flutter', 'download-Flutter',
+ minVersion: IntelliJPlugins.kMinFlutterPluginVersion,
+ );
+
+ final ValidationMessage message = messages.firstWhere(
+ (ValidationMessage m) => m.message.startsWith('Flutter '));
+ expect(message.message, contains('Flutter plugin version 50.0'));
+ });
+
+ testWithoutContext('IntelliJPlugins can read the package version of the flutter-intellij 50.0+/IntelliJ 2020.2+ layout(priority is given to packages with the same prefix as packageName)', () async {
+ final IntelliJPlugins plugins = IntelliJPlugins(_kPluginsPath, fileSystem: fileSystem);
+
+ final Archive flutterIdeaJarArchive = buildSingleFileArchive('META-INF/plugin.xml', r'''
+<idea-plugin version="2">
+<name>Flutter</name>
+<version>50.0</version>
+</idea-plugin>
+''');
+ writeFileCreatingDirectories(
+ fileSystem.path.join(_kPluginsPath, 'flutter-intellij', 'lib', 'flutter-idea-50.0.jar'),
+ ZipEncoder().encode(flutterIdeaJarArchive),
+ );
+ final Archive flutterIntellijJarArchive = buildSingleFileArchive('META-INF/plugin.xml', r'''
+<idea-plugin version="2">
+<name>Flutter</name>
+<version>51.0</version>
+</idea-plugin>
+''');
+ writeFileCreatingDirectories(
+ fileSystem.path.join(_kPluginsPath, 'flutter-intellij', 'lib', 'flutter-intellij-50.0.jar'),
+ ZipEncoder().encode(flutterIntellijJarArchive),
+ );
+
+ final List<ValidationMessage> messages = <ValidationMessage>[];
+ plugins.validatePackage(messages,
+ <String>[
+ 'flutter-intellij',
+ 'flutter-intellij.jar',
+ ],
+ 'Flutter', 'download-Flutter',
+ minVersion: IntelliJPlugins.kMinFlutterPluginVersion,
+ );
+
+ final ValidationMessage message = messages.firstWhere(
+ (ValidationMessage m) => m.message.startsWith('Flutter '));
+ expect(message.message, contains('Flutter plugin version 51.0'));
+ });
+
testWithoutContext('IntelliJPlugins not found displays a link to their download site', () async {
final IntelliJPlugins plugins = IntelliJPlugins(_kPluginsPath, fileSystem: fileSystem);
@@ -94,11 +169,8 @@
final IntelliJPlugins plugins = IntelliJPlugins(_kPluginsPath, fileSystem: fileSystem);
final Archive dartJarArchive =
- buildSingleFileArchive('META-INF/plugin.xml', r'''
-<idea-plugin version="2">
-<name>Dart</name>
-<version>162.2485</version>
-</idea-plugin>
+ buildSingleFileArchive('META-INF/MANIFEST.MF', r'''
+Manifest-Version: 1.0
''');
writeFileCreatingDirectories(
fileSystem.path.join(_kPluginsPath, 'Dart', 'lib', 'Other.jar'),
diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart
index 91555f8..0761517 100644
--- a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart
+++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart
@@ -301,7 +301,7 @@
..writeAsStringSync('Existing release config');
final FlutterProject project = FlutterProject.fromPath('project');
- await injectPlugins(project, checkProjects: true);
+ await injectPlugins(project, iosPlatform: true);
final String debugContents = projectUnderTest.ios.xcodeConfigFor('Debug').readAsStringSync();
expect(debugContents, contains(
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index bcf5481..e470b90 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -514,7 +514,7 @@
when(iosProject.existsSync()).thenReturn(true);
when(macosProject.existsSync()).thenReturn(true);
- await refreshPluginsList(flutterProject);
+ await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
expect(iosProject.podManifestLock.existsSync(), false);
expect(macosProject.podManifestLock.existsSync(), false);
}, overrides: <Type, Generator>{
@@ -533,7 +533,7 @@
// Since there was no plugins list, the lock files will be invalidated.
// The second call is where the plugins list is compared to the existing one, and if there is no change,
// the podfiles shouldn't be invalidated.
- await refreshPluginsList(flutterProject);
+ await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
simulatePodInstallRun(iosProject);
simulatePodInstallRun(macosProject);
@@ -549,16 +549,9 @@
});
group('injectPlugins', () {
- MockFeatureFlags featureFlags;
MockXcodeProjectInterpreter xcodeProjectInterpreter;
setUp(() {
- featureFlags = MockFeatureFlags();
- when(featureFlags.isLinuxEnabled).thenReturn(false);
- when(featureFlags.isMacOSEnabled).thenReturn(false);
- when(featureFlags.isWindowsEnabled).thenReturn(false);
- when(featureFlags.isWebEnabled).thenReturn(false);
-
xcodeProjectInterpreter = MockXcodeProjectInterpreter();
when(xcodeProjectInterpreter.isInstalled).thenReturn(false);
});
@@ -567,7 +560,7 @@
when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -580,14 +573,13 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Registrant uses new embedding if app uses new embedding', () async {
when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -600,7 +592,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Registrant uses shim for plugins using old embedding if app uses new embedding', () async {
@@ -611,7 +602,7 @@
createNewKotlinPlugin2();
createOldJavaPlugin('plugin3');
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -629,7 +620,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -641,7 +631,7 @@
await expectLater(
() async {
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
},
throwsToolExit(
message: 'The plugin `plugin1` requires your app to be migrated to the Android embedding v2. '
@@ -651,7 +641,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -664,7 +653,7 @@
await expectLater(
() async {
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
},
throwsToolExit(
message: "The plugin `plugin1` doesn't have a main class defined in "
@@ -678,7 +667,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -688,7 +676,7 @@
createDualSupportJavaPlugin4();
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -702,7 +690,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -712,7 +699,7 @@
createDualSupportJavaPlugin4();
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -726,7 +713,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -734,7 +720,7 @@
when(flutterProject.isModule).thenReturn(true);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -747,7 +733,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Module using old plugin shows warning', () async {
@@ -756,7 +741,7 @@
createOldJavaPlugin('plugin3');
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -767,7 +752,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -777,7 +761,7 @@
createNewJavaPlugin1();
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -789,7 +773,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -799,7 +782,7 @@
createDualSupportJavaPlugin4();
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -811,7 +794,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -822,7 +804,7 @@
createOldJavaPlugin('plugin3');
createOldJavaPlugin('plugin4');
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
@@ -836,7 +818,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
@@ -846,7 +827,7 @@
final File manifest = fs.file('AndroidManifest.xml');
when(androidProject.appManifestFile).thenReturn(manifest);
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, androidPlatform: true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
@@ -855,9 +836,6 @@
testUsingContext("Registrant for web doesn't escape slashes in imports", () async {
when(flutterProject.isModule).thenReturn(true);
- when(featureFlags.isWebEnabled).thenReturn(true);
- when(webProject.existsSync()).thenReturn(true);
-
final Directory webPluginWithNestedFile =
fs.systemTempDirectory.createTempSync('web_plugin_with_nested');
webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync('''
@@ -880,7 +858,7 @@
web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toString()}
''');
- await injectPlugins(flutterProject);
+ await injectPlugins(flutterProject, webPlatform: true);
final File registrant = flutterProject.directory
.childDirectory('lib')
@@ -891,12 +869,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated macos registrant, but does not include Dart-only plugins', () async {
- when(macosProject.existsSync()).thenReturn(true);
- when(featureFlags.isMacOSEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(true);
// Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs);
@@ -908,7 +883,7 @@
dartPluginClass: SomePlugin
''');
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, macOSPlatform: true);
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
@@ -917,12 +892,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('pluginClass: none doesn\'t trigger registrant entry on macOS', () async {
- when(macosProject.existsSync()).thenReturn(true);
- when(featureFlags.isMacOSEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(true);
// Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs);
@@ -935,7 +907,7 @@
dartPluginClass: SomePlugin
''');
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, macOSPlatform: true);
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
@@ -945,12 +917,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Invalid yaml does not crash plugin lookup.', () async {
- when(macosProject.existsSync()).thenReturn(true);
- when(featureFlags.isMacOSEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(true);
// Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs);
@@ -958,7 +927,7 @@
"aws ... \"Branch\": $BITBUCKET_BRANCH, \"Date\": $(date +"%m-%d-%y"), \"Time\": $(date +"%T")}\"
''');
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, macOSPlatform: true);
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
@@ -966,16 +935,13 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Linux registrant', () async {
- when(linuxProject.existsSync()).thenReturn(true);
- when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
createFakePlugin(fs);
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, linuxPlatform: true);
final File registrantHeader = linuxProject.managedDirectory.childFile('generated_plugin_registrant.h');
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
@@ -986,12 +952,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Linux registrant, but does not include Dart-only plugins', () async {
- when(linuxProject.existsSync()).thenReturn(true);
- when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs);
@@ -1003,7 +966,7 @@
dartPluginClass: SomePlugin
''');
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, linuxPlatform: true);
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
@@ -1013,12 +976,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('pluginClass: none doesn\'t trigger registrant entry on Linux', () async {
- when(linuxProject.existsSync()).thenReturn(true);
- when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs);
@@ -1031,7 +991,7 @@
dartPluginClass: SomePlugin
''');
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, linuxPlatform: true);
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
@@ -1041,16 +1001,13 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Linux plugin Cmake file', () async {
- when(linuxProject.existsSync()).thenReturn(true);
- when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
createFakePlugin(fs);
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, linuxPlatform: true);
final File pluginMakefile = linuxProject.generatedPluginCmakeFile;
@@ -1063,12 +1020,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Generated Linux plugin files sorts by plugin name', () async {
- when(linuxProject.existsSync()).thenReturn(true);
- when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
createFakePlugins(fs, <String>[
'plugin_d',
@@ -1077,7 +1031,7 @@
'/local_plugins/plugin_b'
]);
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, linuxPlatform: true);
final File pluginCmakeFile = linuxProject.generatedPluginCmakeFile;
final File pluginRegistrant = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
@@ -1090,16 +1044,13 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Windows registrant', () async {
- when(windowsProject.existsSync()).thenReturn(true);
- when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
createFakePlugin(fs);
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, windowsPlatform: true);
final File registrantHeader = windowsProject.managedDirectory.childFile('generated_plugin_registrant.h');
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
@@ -1110,12 +1061,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Windows registrant, but does not include Dart-only plugins', () async {
- when(windowsProject.existsSync()).thenReturn(true);
- when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs);
@@ -1127,7 +1075,7 @@
dartPluginClass: SomePlugin
''');
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, windowsPlatform: true);
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
@@ -1136,12 +1084,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('pluginClass: none doesn\'t trigger registrant entry on Windows', () async {
- when(windowsProject.existsSync()).thenReturn(true);
- when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs);
@@ -1154,7 +1099,7 @@
dartPluginClass: SomePlugin
''');
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, windowsPlatform: true);
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
@@ -1164,12 +1109,9 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Generated Windows plugin files sorts by plugin name', () async {
- when(windowsProject.existsSync()).thenReturn(true);
- when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
createFakePlugins(fs, <String>[
'plugin_d',
@@ -1178,7 +1120,7 @@
'/local_plugins/plugin_b'
]);
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, windowsPlatform: true);
final File pluginCmakeFile = windowsProject.generatedPluginCmakeFile;
final File pluginRegistrant = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
@@ -1191,7 +1133,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
testUsingContext('Generated plugin CMake files always use posix-style paths', () async {
@@ -1199,13 +1140,9 @@
setUpProject(fsWindows);
createFakePlugin(fsWindows);
- when(linuxProject.existsSync()).thenReturn(true);
- when(windowsProject.existsSync()).thenReturn(true);
- when(featureFlags.isLinuxEnabled).thenReturn(true);
- when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
- await injectPlugins(flutterProject, checkProjects: true);
+ await injectPlugins(flutterProject, linuxPlatform: true, windowsPlatform: true);
for (final CmakeBasedProject project in <CmakeBasedProject>[linuxProject, windowsProject]) {
final File pluginCmakefile = project.generatedPluginCmakeFile;
@@ -1217,7 +1154,6 @@
}, overrides: <Type, Generator>{
FileSystem: () => fsWindows,
ProcessManager: () => FakeProcessManager.any(),
- FeatureFlags: () => featureFlags,
});
});
diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart
index 402fcf6..0897187 100644
--- a/packages/flutter_tools/test/general.shard/project_test.dart
+++ b/packages/flutter_tools/test/general.shard/project_test.dart
@@ -116,12 +116,12 @@
FlutterManifest.empty(logger: logger),
FlutterManifest.empty(logger: logger),
);
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectNotExists(project.directory);
});
_testInMemory('does nothing in plugin or package root project', () async {
final FlutterProject project = await aPluginProject();
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectNotExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
expectNotExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
@@ -129,22 +129,22 @@
});
_testInMemory('injects plugins for iOS', () async {
final FlutterProject project = await someProject();
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
});
_testInMemory('generates Xcode configuration for iOS', () async {
final FlutterProject project = await someProject();
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
});
_testInMemory('injects plugins for Android', () async {
final FlutterProject project = await someProject();
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
});
_testInMemory('updates local properties for Android', () async {
final FlutterProject project = await someProject();
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
});
_testInMemory('Android project not on v2 embedding shows a warning', () async {
@@ -153,18 +153,18 @@
// v1 embedding, as opposed to having <meta-data
// android:name="flutterEmbedding" android:value="2" />.
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expect(testLogger.statusText, contains('https://flutter.dev/go/android-project-migration'));
});
_testInMemory('updates local properties for Android', () async {
final FlutterProject project = await someProject();
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
});
testUsingContext('injects plugins for macOS', () async {
final FlutterProject project = await someProject();
project.macos.managedDirectory.createSync(recursive: true);
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.macos.managedDirectory.childFile('GeneratedPluginRegistrant.swift'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
@@ -178,7 +178,7 @@
testUsingContext('generates Xcode configuration for macOS', () async {
final FlutterProject project = await someProject();
project.macos.managedDirectory.createSync(recursive: true);
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.macos.generatedXcodePropertiesFile);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
@@ -192,7 +192,7 @@
testUsingContext('injects plugins for Linux', () async {
final FlutterProject project = await someProject();
project.linux.cmakeFile.createSync(recursive: true);
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.cc'));
}, overrides: <Type, Generator>{
@@ -207,7 +207,7 @@
testUsingContext('injects plugins for Windows', () async {
final FlutterProject project = await someProject();
project.windows.cmakeFile.createSync(recursive: true);
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.cc'));
}, overrides: <Type, Generator>{
@@ -221,14 +221,14 @@
});
_testInMemory('creates Android library in module', () async {
final FlutterProject project = await aModuleProject();
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
expectExists(project.android.hostAppGradleRoot.childFile('settings.gradle'));
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('Flutter')));
});
_testInMemory('creates iOS pod in module', () async {
final FlutterProject project = await aModuleProject();
- await project.ensureReadyForPlatformSpecificTooling();
+ await project.regeneratePlatformSpecificTooling();
final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter');
expectExists(flutter.childFile('podhelper.rb'));
expectExists(flutter.childFile('flutter_export_environment.sh'));
diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
index 0ecc594..969077b 100644
--- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
@@ -32,6 +32,7 @@
void main() {
Testbed testbed;
WebAssetServer webAssetServer;
+ ReleaseAssetServer releaseAssetServer;
Platform linux;
PackageConfig packages;
Platform windows;
@@ -56,6 +57,14 @@
null,
null,
);
+ releaseAssetServer = ReleaseAssetServer(
+ globals.fs.file('main.dart').uri,
+ fileSystem: null,
+ flutterRoot: null,
+ platform: null,
+ webBuildDirectory: null,
+ basePath: null,
+ );
});
});
@@ -924,6 +933,20 @@
expect(webAssetServer.defaultResponseHeaders['x-frame-options'], null);
await webAssetServer.dispose();
});
+
+ test('WebAssetServer responds to POST requests with 404 not found', () => testbed.run(() async {
+ final Response response = await webAssetServer.handleRequest(
+ Request('POST', Uri.parse('http://foobar/something')),
+ );
+ expect(response.statusCode, 404);
+ }));
+
+ test('ReleaseAssetServer responds to POST requests with 404 not found', () => testbed.run(() async {
+ final Response response = await releaseAssetServer.handle(
+ Request('POST', Uri.parse('http://foobar/something')),
+ );
+ expect(response.statusCode, 404);
+ }));
}
class MockHttpServer extends Mock implements HttpServer {}
diff --git a/packages/flutter_tools/tool/daemon_client.dart b/packages/flutter_tools/tool/daemon_client.dart
index 1c3b5ef..abc9e58 100644
--- a/packages/flutter_tools/tool/daemon_client.dart
+++ b/packages/flutter_tools/tool/daemon_client.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// @dart = 2.9
+
import 'dart:convert';
import 'dart:io';
diff --git a/packages/flutter_tools/tool/global_count.dart b/packages/flutter_tools/tool/global_count.dart
index 89b18dc..a1c269e 100644
--- a/packages/flutter_tools/tool/global_count.dart
+++ b/packages/flutter_tools/tool/global_count.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// @dart = 2.9
+
import 'dart:io';
import 'package:path/path.dart' as path;
diff --git a/packages/flutter_tools/tool/screenshot_decoder.dart b/packages/flutter_tools/tool/screenshot_decoder.dart
index 9e09e80..47eb157 100644
--- a/packages/flutter_tools/tool/screenshot_decoder.dart
+++ b/packages/flutter_tools/tool/screenshot_decoder.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// @dart = 2.9
+
import 'dart:convert';
import 'dart:io';
diff --git a/packages/flutter_tools/tool/unit_coverage.dart b/packages/flutter_tools/tool/unit_coverage.dart
index 7f36c36..1e7518c 100644
--- a/packages/flutter_tools/tool/unit_coverage.dart
+++ b/packages/flutter_tools/tool/unit_coverage.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// @dart = 2.9
+
import 'dart:io';
/// Produces a per-library coverage summary when fed an lcov file, sorted by