Unverified Commit 32aa3128 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Analyze code snippets in flutter_driver docs (#132337)

parent 33b66f64
...@@ -55,10 +55,12 @@ ...@@ -55,10 +55,12 @@
// //
// At the top of a file you can say `// Examples can assume:` and then list some // At the top of a file you can say `// Examples can assume:` and then list some
// commented-out declarations that will be included in the analysis for snippets // commented-out declarations that will be included in the analysis for snippets
// in that file. // in that file. This section may also contain explicit import statements.
// //
// Snippets generally import all the main Flutter packages (including material // For files without an `// Examples can assume:` section or if that section
// and flutter_test), as well as most core Dart packages with the usual prefixes. // contains no explicit imports, the snippets will implicitly import all the
// main Flutter packages (including material and flutter_test), as well as most
// core Dart packages with the usual prefixes.
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
...@@ -72,6 +74,7 @@ import 'package:watcher/watcher.dart'; ...@@ -72,6 +74,7 @@ import 'package:watcher/watcher.dart';
final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script)))); final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
final String _packageFlutter = path.join(_flutterRoot, 'packages', 'flutter', 'lib'); final String _packageFlutter = path.join(_flutterRoot, 'packages', 'flutter', 'lib');
final String _packageFlutterTest = path.join(_flutterRoot, 'packages', 'flutter_test', 'lib'); final String _packageFlutterTest = path.join(_flutterRoot, 'packages', 'flutter_test', 'lib');
final String _packageFlutterDriver = path.join(_flutterRoot, 'packages', 'flutter_driver', 'lib');
final String _packageIntegrationTest = path.join(_flutterRoot, 'packages', 'integration_test', 'lib'); final String _packageIntegrationTest = path.join(_flutterRoot, 'packages', 'integration_test', 'lib');
final String _defaultDartUiLocation = path.join(_flutterRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui'); final String _defaultDartUiLocation = path.join(_flutterRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui');
final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter'); final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
...@@ -153,7 +156,8 @@ Future<void> main(List<String> arguments) async { ...@@ -153,7 +156,8 @@ Future<void> main(List<String> arguments) async {
Directory(_packageFlutter), Directory(_packageFlutter),
Directory(_packageFlutterTest), Directory(_packageFlutterTest),
Directory(_packageIntegrationTest), Directory(_packageIntegrationTest),
// TODO(goderbauer): Add all other packages. Directory(_packageFlutterDriver),
// TODO(goderbauer): Add all other packages for which we publish docs.
]; ];
} }
...@@ -574,6 +578,7 @@ class _SnippetChecker { ...@@ -574,6 +578,7 @@ class _SnippetChecker {
final List<String> fileLines = file.readAsLinesSync(); final List<String> fileLines = file.readAsLinesSync();
final List<_Line> ignorePreambleLinesOnly = <_Line>[]; final List<_Line> ignorePreambleLinesOnly = <_Line>[];
final List<_Line> preambleLines = <_Line>[]; final List<_Line> preambleLines = <_Line>[];
final List<_Line> customImports = <_Line>[];
bool inExamplesCanAssumePreamble = false; // Whether or not we're in the file-wide preamble section ("Examples can assume"). bool inExamplesCanAssumePreamble = false; // Whether or not we're in the file-wide preamble section ("Examples can assume").
bool inToolSection = false; // Whether or not we're in a code snippet bool inToolSection = false; // Whether or not we're in a code snippet
bool inDartSection = false; // Whether or not we're in a '```dart' segment. bool inDartSection = false; // Whether or not we're in a '```dart' segment.
...@@ -592,7 +597,11 @@ class _SnippetChecker { ...@@ -592,7 +597,11 @@ class _SnippetChecker {
throw _SnippetCheckerException('Unexpected content in snippet code preamble.', file: relativeFilePath, line: lineNumber); throw _SnippetCheckerException('Unexpected content in snippet code preamble.', file: relativeFilePath, line: lineNumber);
} else { } else {
final _Line newLine = _Line(line: lineNumber, indent: 3, code: line.substring(3)); final _Line newLine = _Line(line: lineNumber, indent: 3, code: line.substring(3));
if (newLine.code.startsWith('import ')) {
customImports.add(newLine);
} else {
preambleLines.add(newLine); preambleLines.add(newLine);
}
if (line.startsWith('// // ignore_for_file: ')) { if (line.startsWith('// // ignore_for_file: ')) {
ignorePreambleLinesOnly.add(newLine); ignorePreambleLinesOnly.add(newLine);
} }
...@@ -612,7 +621,7 @@ class _SnippetChecker { ...@@ -612,7 +621,7 @@ class _SnippetChecker {
} }
if (trimmedLine.startsWith(_codeBlockEndRegex)) { if (trimmedLine.startsWith(_codeBlockEndRegex)) {
inDartSection = false; inDartSection = false;
final _SnippetFile snippet = _processBlock(startLine, block, preambleLines, ignorePreambleLinesOnly, relativeFilePath, lastExample); final _SnippetFile snippet = _processBlock(startLine, block, preambleLines, ignorePreambleLinesOnly, relativeFilePath, lastExample, customImports);
final String path = _writeSnippetFile(snippet).path; final String path = _writeSnippetFile(snippet).path;
assert(!snippetMap.containsKey(path)); assert(!snippetMap.containsKey(path));
snippetMap[path] = snippet; snippetMap[path] = snippet;
...@@ -655,6 +664,7 @@ class _SnippetChecker { ...@@ -655,6 +664,7 @@ class _SnippetChecker {
line.contains('```kotlin') || line.contains('```kotlin') ||
line.contains('```swift') || line.contains('```swift') ||
line.contains('```glsl') || line.contains('```glsl') ||
line.contains('```json') ||
line.contains('```csv')) { line.contains('```csv')) {
inOtherBlock = true; inOtherBlock = true;
} else if (line.startsWith(_uncheckedCodeBlockStartRegex)) { } else if (line.startsWith(_uncheckedCodeBlockStartRegex)) {
...@@ -694,7 +704,7 @@ class _SnippetChecker { ...@@ -694,7 +704,7 @@ class _SnippetChecker {
/// a primitive heuristic to make snippet blocks into valid Dart code. /// a primitive heuristic to make snippet blocks into valid Dart code.
/// ///
/// `block` argument will get mutated, but is copied before this function returns. /// `block` argument will get mutated, but is copied before this function returns.
_SnippetFile _processBlock(_Line startingLine, List<String> block, List<_Line> assumptions, List<_Line> ignoreAssumptionsOnly, String filename, _SnippetFile? lastExample) { _SnippetFile _processBlock(_Line startingLine, List<String> block, List<_Line> assumptions, List<_Line> ignoreAssumptionsOnly, String filename, _SnippetFile? lastExample, List<_Line> customImports) {
if (block.isEmpty) { if (block.isEmpty) {
throw _SnippetCheckerException('${startingLine.asLocation(filename, 0)}: Empty ```dart block in snippet code.'); throw _SnippetCheckerException('${startingLine.asLocation(filename, 0)}: Empty ```dart block in snippet code.');
} }
...@@ -755,7 +765,7 @@ class _SnippetChecker { ...@@ -755,7 +765,7 @@ class _SnippetChecker {
return _SnippetFile.fromStrings( return _SnippetFile.fromStrings(
startingLine, startingLine,
block.toList(), block.toList(),
importPreviousExample ? <_Line>[] : headersWithoutImports, headersWithoutImports,
<_Line>[ <_Line>[
...ignoreAssumptionsOnly, ...ignoreAssumptionsOnly,
if (hasEllipsis) if (hasEllipsis)
...@@ -764,13 +774,24 @@ class _SnippetChecker { ...@@ -764,13 +774,24 @@ class _SnippetChecker {
'self-contained program', 'self-contained program',
filename, filename,
); );
} else if (hasStatefulWidgetComment) { }
final List<_Line> headers = switch ((importPreviousExample, customImports.length)) {
(true, _) => <_Line>[],
(false, 0) => headersWithImports,
(false, _) => <_Line>[
...headersWithoutImports,
const _Line.generated(code: '// ignore_for_file: unused_import'),
...customImports,
]
};
if (hasStatefulWidgetComment) {
return _SnippetFile.fromStrings( return _SnippetFile.fromStrings(
startingLine, startingLine,
prefix: 'class _State extends State<StatefulWidget> {', prefix: 'class _State extends State<StatefulWidget> {',
block.toList(), block.toList(),
postfix: '}', postfix: '}',
importPreviousExample ? <_Line>[] : headersWithImports, headers,
preamble, preamble,
'stateful widget', 'stateful widget',
filename, filename,
...@@ -782,7 +803,7 @@ class _SnippetChecker { ...@@ -782,7 +803,7 @@ class _SnippetChecker {
return _SnippetFile.fromStrings( return _SnippetFile.fromStrings(
startingLine, startingLine,
block.toList(), block.toList(),
importPreviousExample ? <_Line>[] : headersWithImports, headers,
preamble, preamble,
'top-level declaration', 'top-level declaration',
filename, filename,
...@@ -795,7 +816,7 @@ class _SnippetChecker { ...@@ -795,7 +816,7 @@ class _SnippetChecker {
prefix: 'Future<void> function() async {', prefix: 'Future<void> function() async {',
block.toList(), block.toList(),
postfix: '}', postfix: '}',
importPreviousExample ? <_Line>[] : headersWithImports, headers,
preamble, preamble,
'statement', 'statement',
filename, filename,
...@@ -807,7 +828,7 @@ class _SnippetChecker { ...@@ -807,7 +828,7 @@ class _SnippetChecker {
prefix: 'class Class {', prefix: 'class Class {',
block.toList(), block.toList(),
postfix: '}', postfix: '}',
importPreviousExample ? <_Line>[] : headersWithImports, headers,
<_Line>[ <_Line>[
...preamble, ...preamble,
const _Line.generated(code: '// ignore_for_file: avoid_classes_with_only_static_members'), const _Line.generated(code: '// ignore_for_file: avoid_classes_with_only_static_members'),
...@@ -849,7 +870,7 @@ class _SnippetChecker { ...@@ -849,7 +870,7 @@ class _SnippetChecker {
prefix: 'dynamic expression = ', prefix: 'dynamic expression = ',
block.toList(), block.toList(),
postfix: ';', postfix: ';',
importPreviousExample ? <_Line>[] : headersWithImports, headers,
preamble, preamble,
'expression', 'expression',
filename, filename,
......
// 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.
// This file is used by ../analyze_snippet_code_test.dart, which depends on the
// precise contents (including especially the comments) of this file.
// Examples can assume:
// import 'package:flutter/rendering.dart';
/// no error: rendering library was imported.
/// ```dart
/// print(RenderObject);
/// ```
String? bar;
/// error: widgets library was not imported (not even implicitly).
/// ```dart
/// print(Widget);
/// ```
String? foo;
...@@ -11,6 +11,7 @@ import 'dart:io'; ...@@ -11,6 +11,7 @@ import 'dart:io';
import 'common.dart'; import 'common.dart';
const List<String> expectedMainErrors = <String>[ const List<String> expectedMainErrors = <String>[
'dev/bots/test/analyze-snippet-code-test-input/custom_imports_broken.dart:19:11: (statement) (undefined_identifier)',
'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:30:5: (expression) (unnecessary_new)', 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:30:5: (expression) (unnecessary_new)',
'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:103:5: (statement) (always_specify_types)', 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:103:5: (statement) (always_specify_types)',
'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:111:5: (top-level declaration) (prefer_const_declarations)', 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:111:5: (top-level declaration) (prefer_const_declarations)',
...@@ -68,7 +69,7 @@ void main() { ...@@ -68,7 +69,7 @@ void main() {
final List<String> stderrNoDescriptions = stderrLines.map(removeLintDescriptions).toList(); final List<String> stderrNoDescriptions = stderrLines.map(removeLintDescriptions).toList();
expect(stderrNoDescriptions, <String>[ expect(stderrNoDescriptions, <String>[
...expectedMainErrors, ...expectedMainErrors,
'Found 15 snippet code errors.', 'Found 16 snippet code errors.',
'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.', 'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.',
'', // because we end with a newline, split gives us an extra blank line '', // because we end with a newline, split gives us an extra blank line
]); ]);
...@@ -92,7 +93,7 @@ void main() { ...@@ -92,7 +93,7 @@ void main() {
expect(stderrNoDescriptions, <String>[ expect(stderrNoDescriptions, <String>[
...expectedUiErrors, ...expectedUiErrors,
...expectedMainErrors, ...expectedMainErrors,
'Found 19 snippet code errors.', 'Found 20 snippet code errors.',
'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.', 'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.',
'', // because we end with a newline, split gives us an extra blank line '', // because we end with a newline, split gives us an extra blank line
]); ]);
......
...@@ -83,6 +83,11 @@ const CommonFinders find = CommonFinders._(); ...@@ -83,6 +83,11 @@ const CommonFinders find = CommonFinders._();
/// See also [FlutterDriver.waitFor]. /// See also [FlutterDriver.waitFor].
typedef EvaluatorFunction = dynamic Function(); typedef EvaluatorFunction = dynamic Function();
// Examples can assume:
// import 'package:flutter_driver/flutter_driver.dart';
// import 'package:test/test.dart';
// late FlutterDriver driver;
/// Drives a Flutter Application running in another process. /// Drives a Flutter Application running in another process.
abstract class FlutterDriver { abstract class FlutterDriver {
/// Default constructor. /// Default constructor.
...@@ -478,7 +483,7 @@ abstract class FlutterDriver { ...@@ -478,7 +483,7 @@ abstract class FlutterDriver {
/// ///
/// ```dart /// ```dart
/// test('enters text in a text field', () async { /// test('enters text in a text field', () async {
/// var textField = find.byValueKey('enter-text-field'); /// final SerializableFinder textField = find.byValueKey('enter-text-field');
/// await driver.tap(textField); // acquire focus /// await driver.tap(textField); // acquire focus
/// await driver.enterText('Hello!'); // enter text /// await driver.enterText('Hello!'); // enter text
/// await driver.waitFor(find.text('Hello!')); // verify text appears on UI /// await driver.waitFor(find.text('Hello!')); // verify text appears on UI
...@@ -520,7 +525,7 @@ abstract class FlutterDriver { ...@@ -520,7 +525,7 @@ abstract class FlutterDriver {
/// ///
/// ```dart /// ```dart
/// test('submit text in a text field', () async { /// test('submit text in a text field', () async {
/// var textField = find.byValueKey('enter-text-field'); /// final SerializableFinder textField = find.byValueKey('enter-text-field');
/// await driver.tap(textField); // acquire focus /// await driver.tap(textField); // acquire focus
/// await driver.enterText('Hello!'); // enter text /// await driver.enterText('Hello!'); // enter text
/// await driver.waitFor(find.text('Hello!')); // verify text appears on UI /// await driver.waitFor(find.text('Hello!')); // verify text appears on UI
......
...@@ -17,7 +17,7 @@ const Set<String> kGCRootEvents = <String>{ ...@@ -17,7 +17,7 @@ const Set<String> kGCRootEvents = <String>{
/// Summarizes [TimelineEvents]s corresponding to [kGCRootEvents] category. /// Summarizes [TimelineEvents]s corresponding to [kGCRootEvents] category.
/// ///
/// A sample event (some fields have been omitted for brevity): /// A sample event (some fields have been omitted for brevity):
/// ``` /// ```json
/// { /// {
/// "name": "StartConcurrentMarking", /// "name": "StartConcurrentMarking",
/// "cat": "GC", /// "cat": "GC",
......
...@@ -36,7 +36,7 @@ enum ProfileType { ...@@ -36,7 +36,7 @@ enum ProfileType {
/// Summarizes [TimelineEvents]s corresponding to [kProfilingEvents] category. /// Summarizes [TimelineEvents]s corresponding to [kProfilingEvents] category.
/// ///
/// A sample event (some fields have been omitted for brevity): /// A sample event (some fields have been omitted for brevity):
/// ``` /// ```json
/// { /// {
/// "category": "embedder", /// "category": "embedder",
/// "name": "CpuUsage", /// "name": "CpuUsage",
......
...@@ -16,7 +16,7 @@ const String _kPictureMemory = 'PictureMBytes'; ...@@ -16,7 +16,7 @@ const String _kPictureMemory = 'PictureMBytes';
/// Summarizes [TimelineEvents]s corresponding to [kRasterCacheEvent] events. /// Summarizes [TimelineEvents]s corresponding to [kRasterCacheEvent] events.
/// ///
/// A sample event (some fields have been omitted for brevity): /// A sample event (some fields have been omitted for brevity):
/// ``` /// ```json
/// { /// {
/// "name": "RasterCache", /// "name": "RasterCache",
/// "ts": 75598996256, /// "ts": 75598996256,
......
...@@ -13,7 +13,7 @@ const String _kVsyncTransitionsMissed = 'vsync_transitions_missed'; ...@@ -13,7 +13,7 @@ const String _kVsyncTransitionsMissed = 'vsync_transitions_missed';
/// Summarizes [TimelineEvents]s corresponding to [kSceneDisplayLagEvent] events. /// Summarizes [TimelineEvents]s corresponding to [kSceneDisplayLagEvent] events.
/// ///
/// A sample event (some fields have been omitted for brevity): /// A sample event (some fields have been omitted for brevity):
/// ``` /// ```json
/// { /// {
/// "name": "SceneDisplayLag", /// "name": "SceneDisplayLag",
/// "ts": 408920509340, /// "ts": 408920509340,
......
...@@ -58,6 +58,19 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, ...@@ -58,6 +58,19 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
} }
} }
// Examples can assume:
// import 'package:flutter_driver/flutter_driver.dart';
// import 'package:flutter/widgets.dart';
// import 'package:flutter_driver/driver_extension.dart';
// import 'package:flutter_test/flutter_test.dart' hide find;
// import 'package:flutter_test/flutter_test.dart' as flutter_test;
// typedef MyHomeWidget = Placeholder;
// abstract class SomeWidget extends StatelessWidget { const SomeWidget({super.key, required this.title}); final String title; }
// late FlutterDriver driver;
// abstract class StubNestedCommand { int get times; SerializableFinder get finder; }
// class StubCommandResult extends Result { const StubCommandResult(this.arg); final String arg; @override Map<String, dynamic> toJson() => <String, dynamic>{}; }
// abstract class StubProberCommand { int get times; SerializableFinder get finder; }
/// Enables Flutter Driver VM service extension. /// Enables Flutter Driver VM service extension.
/// ///
/// This extension is required for tests that use `package:flutter_driver` to /// This extension is required for tests that use `package:flutter_driver` to
...@@ -87,105 +100,54 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, ...@@ -87,105 +100,54 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// The `finders` and `commands` parameters are optional and used to add custom /// The `finders` and `commands` parameters are optional and used to add custom
/// finders or commands, as in the following example. /// finders or commands, as in the following example.
/// ///
/// ```dart main /// ```dart
/// void main() { /// void main() {
/// enableFlutterDriverExtension( /// enableFlutterDriverExtension(
/// finders: <FinderExtension>[ SomeFinderExtension() ], /// finders: <FinderExtension>[ SomeFinderExtension() ],
/// commands: <CommandExtension>[ SomeCommandExtension() ], /// commands: <CommandExtension>[ SomeCommandExtension() ],
/// ); /// );
/// ///
/// app.main(); /// runApp(const MyHomeWidget());
/// } /// }
/// ```
///
/// ```dart
/// driver.sendCommand(SomeCommand(ByValueKey('Button'), 7));
/// ```
///
/// `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;
/// ///
/// class SomeFinderExtension extends FinderExtension {
/// @override /// @override
/// String get finderType => 'SomeFinder'; /// String get finderType => 'SomeFinder';
/// ///
/// @override /// @override
/// Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
/// 'title': title,
/// });
/// }
/// ```
///
/// ```dart
/// class SomeFinderExtension extends FinderExtension {
///
/// String get finderType => 'SomeFinder';
///
/// SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory) { /// SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory) {
/// return SomeFinder(json['title']); /// return SomeFinder(params['title']!);
/// } /// }
/// ///
/// @override
/// Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) { /// Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) {
/// Some someFinder = finder as SomeFinder; /// final SomeFinder someFinder = finder as SomeFinder;
/// ///
/// return find.byElementPredicate((Element element) { /// return flutter_test.find.byElementPredicate((Element element) {
/// final Widget widget = element.widget; /// final Widget widget = element.widget;
/// if (element.widget is SomeWidget) { /// if (widget is SomeWidget) {
/// return element.widget.title == someFinder.title; /// return widget.title == someFinder.title;
/// } /// }
/// return false; /// return false;
/// }); /// });
/// } /// }
/// } /// }
/// ```
///
/// `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 /// // Use this class in a test anywhere where a SerializableFinder is expected.
/// class SomeCommand extends CommandWithTarget { /// class SomeFinder extends SerializableFinder {
/// SomeCommand(SerializableFinder finder, this.times, {Duration? timeout}) /// const SomeFinder(this.title);
/// : super(finder, timeout: timeout);
///
/// SomeCommand.deserialize(Map<String, String> json, DeserializeFinderFactory finderFactory)
/// : times = int.parse(json['times']!),
/// super.deserialize(json, finderFactory);
/// ///
/// @override /// final String title;
/// Map<String, String> serialize() {
/// return super.serialize()..addAll(<String, String>{'times': '$times'});
/// }
/// ///
/// @override /// @override
/// String get kind => 'SomeCommand'; /// String get finderType => 'SomeFinder';
///
/// final int times;
/// }
/// ```
///
/// ```dart
/// class SomeCommandResult extends Result {
/// const SomeCommandResult(this.resultParam);
///
/// final String resultParam;
/// ///
/// @override /// @override
/// Map<String, dynamic> toJson() { /// Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
/// return <String, dynamic>{ /// 'title': title,
/// 'resultParam': resultParam, /// });
/// };
/// }
/// } /// }
/// ```
/// ///
/// ```dart
/// class SomeCommandExtension extends CommandExtension { /// class SomeCommandExtension extends CommandExtension {
/// @override /// @override
/// String get commandKind => 'SomeCommand'; /// String get commandKind => 'SomeCommand';
...@@ -195,7 +157,7 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, ...@@ -195,7 +157,7 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// final SomeCommand someCommand = command as SomeCommand; /// final SomeCommand someCommand = command as SomeCommand;
/// ///
/// // Deserialize [Finder]: /// // Deserialize [Finder]:
/// final Finder finder = finderFactory.createFinder(stubCommand.finder); /// final Finder finder = finderFactory.createFinder(someCommand.finder);
/// ///
/// // Wait for [Element]: /// // Wait for [Element]:
/// handlerFactory.waitForElement(finder); /// handlerFactory.waitForElement(finder);
...@@ -204,12 +166,12 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, ...@@ -204,12 +166,12 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// handlerFactory.waitForAbsentElement(finder); /// handlerFactory.waitForAbsentElement(finder);
/// ///
/// // Submit known [Command]s: /// // Submit known [Command]s:
/// for (int index = 0; i < someCommand.times; index++) { /// for (int i = 0; i < someCommand.times; i++) {
/// await handlerFactory.handleCommand(Tap(someCommand.finder), prober, finderFactory); /// await handlerFactory.handleCommand(Tap(someCommand.finder), prober, finderFactory);
/// } /// }
/// ///
/// // Alternatively, use [WidgetController]: /// // Alternatively, use [WidgetController]:
/// for (int index = 0; i < stubCommand.times; index++) { /// for (int i = 0; i < someCommand.times; i++) {
/// await prober.tap(finder); /// await prober.tap(finder);
/// } /// }
/// ///
...@@ -221,8 +183,40 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, ...@@ -221,8 +183,40 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding,
/// return SomeCommand.deserialize(params, finderFactory); /// return SomeCommand.deserialize(params, finderFactory);
/// } /// }
/// } /// }
/// ```
/// ///
/// // Pass an instance of this class to `FlutterDriver.sendCommand` to invoke
/// // the custom command during a test.
/// class SomeCommand extends CommandWithTarget {
/// SomeCommand(super.finder, this.times, {super.timeout});
///
/// SomeCommand.deserialize(super.json, super.finderFactory)
/// : times = int.parse(json['times']!),
/// super.deserialize();
///
/// @override
/// Map<String, String> serialize() {
/// return super.serialize()..addAll(<String, String>{'times': '$times'});
/// }
///
/// @override
/// String get kind => 'SomeCommand';
///
/// final int times;
/// }
///
/// class SomeCommandResult extends Result {
/// const SomeCommandResult(this.resultParam);
///
/// final String resultParam;
///
/// @override
/// Map<String, dynamic> toJson() {
/// return <String, dynamic>{
/// 'resultParam': resultParam,
/// };
/// }
/// }
/// ```
void enableFlutterDriverExtension({ DataHandler? handler, bool silenceErrors = false, bool enableTextEntryEmulation = true, List<FinderExtension>? finders, List<CommandExtension>? commands}) { void enableFlutterDriverExtension({ DataHandler? handler, bool silenceErrors = false, bool enableTextEntryEmulation = true, List<FinderExtension>? finders, List<CommandExtension>? commands}) {
_DriverBinding(handler, silenceErrors, enableTextEntryEmulation, finders ?? <FinderExtension>[], commands ?? <CommandExtension>[]); _DriverBinding(handler, silenceErrors, enableTextEntryEmulation, finders ?? <FinderExtension>[], commands ?? <CommandExtension>[]);
assert(WidgetsBinding.instance is _DriverBinding); assert(WidgetsBinding.instance is _DriverBinding);
...@@ -287,7 +281,7 @@ abstract class CommandExtension { ...@@ -287,7 +281,7 @@ abstract class CommandExtension {
/// @override /// @override
/// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async { /// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
/// final StubNestedCommand stubCommand = command as StubNestedCommand; /// final StubNestedCommand stubCommand = command as StubNestedCommand;
/// for (int index = 0; i < stubCommand.times; index++) { /// for (int i = 0; i < stubCommand.times; i++) {
/// await handlerFactory.handleCommand(Tap(stubCommand.finder), prober, finderFactory); /// await handlerFactory.handleCommand(Tap(stubCommand.finder), prober, finderFactory);
/// } /// }
/// return const StubCommandResult('stub response'); /// return const StubCommandResult('stub response');
...@@ -300,7 +294,7 @@ abstract class CommandExtension { ...@@ -300,7 +294,7 @@ abstract class CommandExtension {
/// @override /// @override
/// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async { /// Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
/// final StubProberCommand stubCommand = command as StubProberCommand; /// final StubProberCommand stubCommand = command as StubProberCommand;
/// for (int index = 0; i < stubCommand.times; index++) { /// for (int i = 0; i < stubCommand.times; i++) {
/// await prober.tap(finderFactory.createFinder(stubCommand.finder)); /// await prober.tap(finderFactory.createFinder(stubCommand.finder));
/// } /// }
/// return const StubCommandResult('stub response'); /// return const StubCommandResult('stub response');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment