Unverified Commit 64a0683b authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Analyze code snippets in flutter_test docs (#132246)

Fixes https://github.com/flutter/flutter/issues/132274.
parent e11cc350
......@@ -70,7 +70,8 @@ import 'package:path/path.dart' as path;
import 'package:watcher/watcher.dart';
final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
final String _defaultFlutterPackage = 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 _defaultDartUiLocation = path.join(_flutterRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui');
final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
......@@ -142,12 +143,16 @@ Future<void> main(List<String> arguments) async {
exit(0);
}
Directory flutterPackage;
List<Directory> flutterPackages;
if (parsedArguments.rest.length == 1) {
// Used for testing.
flutterPackage = Directory(parsedArguments.rest.single);
flutterPackages = <Directory>[Directory(parsedArguments.rest.single)];
} else {
flutterPackage = Directory(_defaultFlutterPackage);
flutterPackages = <Directory>[
Directory(_packageFlutter),
Directory(_packageFlutterTest),
// TODO(goderbauer): Add all other packages.
];
}
final bool includeDartUi = parsedArguments.wasParsed('dart-ui-location') || parsedArguments['include-dart-ui'] as bool;
......@@ -165,14 +170,14 @@ Future<void> main(List<String> arguments) async {
if (parsedArguments['interactive'] != null) {
await _runInteractive(
flutterPackage: flutterPackage,
flutterPackages: flutterPackages,
tempDirectory: parsedArguments['temp'] as String?,
filePath: parsedArguments['interactive'] as String,
dartUiLocation: includeDartUi ? dartUiLocation : null,
);
} else {
if (await _SnippetChecker(
flutterPackage,
flutterPackages,
tempDirectory: parsedArguments['temp'] as String?,
verbose: parsedArguments['verbose'] as bool,
dartUiLocation: includeDartUi ? dartUiLocation : null,
......@@ -360,7 +365,7 @@ class _SnippetChecker {
/// supplied, the default location of the `dart:ui` code in the Flutter
/// repository is used (i.e. "<flutter repo>/bin/cache/pkg/sky_engine/lib/ui").
_SnippetChecker(
this._flutterPackage, {
this._flutterPackages, {
String? tempDirectory,
this.verbose = false,
Directory? dartUiLocation,
......@@ -438,8 +443,8 @@ class _SnippetChecker {
/// automatically if there are no errors unless _keepTmp is true.
final Directory _tempDirectory;
/// The package directory for the flutter package within the flutter root dir.
final Directory _flutterPackage;
/// The package directories within the flutter root dir that will be checked.
final List<Directory> _flutterPackages;
/// The directory for the dart:ui code to be analyzed with the flutter code.
///
......@@ -481,7 +486,7 @@ class _SnippetChecker {
"import 'dart:typed_data';",
"import 'dart:ui' as ui;",
"import 'package:flutter_test/flutter_test.dart';",
for (final File file in _listDartFiles(Directory(_defaultFlutterPackage)))
for (final File file in _listDartFiles(Directory(_packageFlutter)))
"import 'package:flutter/${path.basename(file.path)}';",
].map<_Line>((String code) => _Line.generated(code: code)).toList();
}
......@@ -495,7 +500,8 @@ class _SnippetChecker {
stderr.writeln('Unable to analyze engine dart snippets at ${_dartUiLocation!.path}.');
}
final List<File> filesToAnalyze = <File>[
..._listDartFiles(_flutterPackage, recursive: true),
for (final Directory flutterPackage in _flutterPackages)
..._listDartFiles(flutterPackage, recursive: true),
if (_dartUiLocation != null && _dartUiLocation!.existsSync())
..._listDartFiles(_dartUiLocation!, recursive: true),
];
......@@ -1084,7 +1090,7 @@ class _SnippetFile {
Future<void> _runInteractive({
required String? tempDirectory,
required Directory flutterPackage,
required List<Directory> flutterPackages,
required String filePath,
required Directory? dartUiLocation,
}) async {
......@@ -1106,7 +1112,7 @@ Future<void> _runInteractive({
print('Starting up in interactive mode on ${path.relative(filePath, from: _flutterRoot)} ...');
print('Type "q" to quit, or "r" to force a reload.');
final _SnippetChecker checker = _SnippetChecker(flutterPackage, tempDirectory: tempDirectory)
final _SnippetChecker checker = _SnippetChecker(flutterPackages, tempDirectory: tempDirectory)
.._createConfigurationFiles();
ProcessSignal.sigint.watch().listen((_) {
......
......@@ -27,7 +27,7 @@
/// with the following signature:
///
/// ```dart
/// Future<void> testExecutable(FutureOr<void> Function() testMain);
/// Future<void> testExecutable(FutureOr<void> Function() testMain) async { }
/// ```
///
/// The test framework will execute that method and pass it the `main()` method
......
......@@ -57,6 +57,9 @@ class Evaluation {
}
}
// Examples can assume:
// typedef HomePage = Placeholder;
/// An accessibility guideline describes a recommendation an application should
/// meet to be considered accessible.
///
......
......@@ -55,14 +55,14 @@ import 'package:flutter/widgets.dart';
/// // Start recording (`recording` is true)
/// await tester.pumpFrames(animationSheet.record(
/// target,
/// recording: true,
/// recording: true, // ignore: avoid_redundant_argument_values
/// ), const Duration(seconds: 1));
///
/// await gesture.up();
///
/// await tester.pumpFrames(animationSheet.record(
/// target,
/// recording: true,
/// recording: true, // ignore: avoid_redundant_argument_values
/// ), const Duration(seconds: 1));
///
/// // Compare against golden file
......
......@@ -143,6 +143,10 @@ class CapturedAccessibilityAnnouncement {
final Assertiveness assertiveness;
}
// Examples can assume:
// late TestWidgetsFlutterBinding binding;
// late Size someSize;
/// Base class for bindings used by widgets library tests.
///
/// The [ensureInitialized] method creates (if necessary) and returns an
......@@ -1548,7 +1552,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
/// ```dart
/// TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
/// if (binding is LiveTestWidgetsFlutterBinding) {
/// binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.[thePolicy];
/// binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.onlyPumps;
/// }
/// ```
/// {@endtemplate}
......
// 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:typed_data';
import 'package:matcher/expect.dart' show Description;
import 'package:matcher/src/expect/async_matcher.dart'; // ignore: implementation_imports
import 'package:test_api/hooks.dart' show TestFailure;
import 'goldens.dart';
/// Matcher created by [bufferMatchesGoldenFile].
class _BufferGoldenMatcher extends AsyncMatcher {
/// Creates an instance of [BufferGoldenMatcher]. Called by [bufferMatchesGoldenFile].
const _BufferGoldenMatcher(this.key, this.version);
/// The [key] to the golden image.
final Uri key;
/// The [version] of the golden image.
final int? version;
@override
Future<String?> matchAsync(dynamic item) async {
Uint8List buffer;
if (item is List<int>) {
buffer = Uint8List.fromList(item);
} else if (item is Future<List<int>>) {
buffer = Uint8List.fromList(await item);
} else {
throw AssertionError('Expected `List<int>` or `Future<List<int>>`, instead found: ${item.runtimeType}');
}
final Uri testNameUri = goldenFileComparator.getTestUri(key, version);
if (autoUpdateGoldenFiles) {
await goldenFileComparator.update(testNameUri, buffer);
return null;
}
try {
final bool success = await goldenFileComparator.compare(buffer, testNameUri);
return success ? null : 'does not match';
} on TestFailure catch (ex) {
return ex.message;
}
}
@override
Description describe(Description description) {
final Uri testNameUri = goldenFileComparator.getTestUri(key, version);
return description.add('Byte buffer matches golden image "$testNameUri"');
}
}
/// Asserts that a [Future<List<int>>], or [List<int] matches the
/// golden image file identified by [key], with an optional [version] number.
///
/// The [key] is the [String] representation of a URL.
///
/// The [version] is a number that can be used to differentiate historical
/// golden files. This parameter is optional.
///
/// {@tool snippet}
/// Sample invocations of [bufferMatchesGoldenFile].
///
/// ```dart
/// await expectLater(
/// const <int>[ /* bytes... */ ],
/// bufferMatchesGoldenFile('sample.png'),
/// );
/// ```
/// {@end-tool}
AsyncMatcher bufferMatchesGoldenFile(String key, {int? version}) {
return _BufferGoldenMatcher(Uri.parse(key), version);
}
......@@ -24,6 +24,9 @@ const double kDragSlopDefault = 20.0;
const String _defaultPlatform = kIsWeb ? 'web' : 'android';
// Examples can assume:
// typedef MyWidget = Placeholder;
/// Class that programmatically interacts with the [Semantics] tree.
///
/// Allows for testing of the [Semantics] tree, which is used by assistive
......@@ -123,13 +126,13 @@ class SemanticsController {
///
/// ## Sample Code
///
/// ```
/// ```dart
/// testWidgets('MyWidget', (WidgetTester tester) async {
/// await tester.pumpWidget(MyWidget());
/// await tester.pumpWidget(const MyWidget());
///
/// expect(
/// tester.semantics.simulatedAccessibilityTraversal(),
/// containsAllInOrder([
/// containsAllInOrder(<Matcher>[
/// containsSemantics(label: 'My Widget'),
/// containsSemantics(label: 'is awesome!', isChecked: true),
/// ]),
......
......@@ -17,6 +17,12 @@ typedef ElementPredicate = bool Function(Element element);
/// Some frequently used widget [Finder]s.
const CommonFinders find = CommonFinders._();
// Examples can assume:
// typedef Button = Placeholder;
// late WidgetTester tester;
// late String filePath;
// late Key backKey;
/// Provides lightweight syntax for getting frequently used widget [Finder]s.
///
/// This class is instantiated once, as [find].
......@@ -116,9 +122,9 @@ class CommonFinders {
///
/// ```dart
/// // Suppose you have a button with text 'Update' in it:
/// Button(
/// const Button(
/// child: Text('Update')
/// )
/// );
///
/// // You can find and tap on it like this:
/// tester.tap(find.widgetWithText(Button, 'Update'));
......@@ -217,9 +223,9 @@ class CommonFinders {
///
/// ```dart
/// // Suppose you have a button with icon 'arrow_forward' in it:
/// Button(
/// const Button(
/// child: Icon(Icons.arrow_forward)
/// )
/// );
///
/// // You can find and tap on it like this:
/// tester.tap(find.widgetWithIcon(Button, Icons.arrow_forward));
......@@ -242,11 +248,11 @@ class CommonFinders {
/// ```dart
/// // Suppose you have a button with image in it:
/// Button(
/// child: Image.file(filePath)
/// )
/// child: Image.file(File(filePath))
/// );
///
/// // You can find and tap on it like this:
/// tester.tap(find.widgetWithImage(Button, FileImage(filePath)));
/// tester.tap(find.widgetWithImage(Button, FileImage(File(filePath))));
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
......@@ -283,7 +289,7 @@ class CommonFinders {
///
/// ```dart
/// // Suppose you have a button created like this:
/// Widget myButton = Button(
/// Widget myButton = const Button(
/// child: Text('Update')
/// );
///
......@@ -396,7 +402,7 @@ class CommonFinders {
/// tester.widget<Opacity>(
/// find.ancestor(
/// of: find.text('faded'),
/// matching: find.byType('Opacity'),
/// matching: find.byType(Opacity),
/// )
/// ).opacity,
/// 0.5
......
......@@ -331,6 +331,13 @@ Matcher isMethodCall(String name, { required dynamic arguments }) {
Matcher coversSameAreaAs(Path expectedPath, { required Rect areaToCompare, int sampleSize = 20 })
=> _CoversSameAreaAs(expectedPath, areaToCompare: areaToCompare, sampleSize: sampleSize);
// Examples can assume:
// late Image image;
// late Future<Image> imageFuture;
// typedef MyWidget = Placeholder;
// late Future<ByteData> someFont;
// late WidgetTester tester;
/// Asserts that a [Finder], [Future<ui.Image>], or [ui.Image] matches the
/// golden image file identified by [key], with an optional [version] number.
///
......@@ -404,18 +411,18 @@ Matcher coversSameAreaAs(Path expectedPath, { required Rect areaToCompare, int s
/// {@tool snippet}
/// How to load a custom font for golden images.
/// ```dart
/// testWidgets('Creating a golden image with a custom font', (tester) async {
/// testWidgets('Creating a golden image with a custom font', (WidgetTester tester) async {
/// // Assuming the 'Roboto.ttf' file is declared in the pubspec.yaml file
/// final font = rootBundle.load('path/to/font-file/Roboto.ttf');
/// final Future<ByteData> font = rootBundle.load('path/to/font-file/Roboto.ttf');
///
/// final fontLoader = FontLoader('Roboto')..addFont(font);
/// final FontLoader fontLoader = FontLoader('Roboto')..addFont(font);
/// await fontLoader.load();
///
/// await tester.pumpWidget(const SomeWidget());
/// await tester.pumpWidget(const MyWidget());
///
/// await expectLater(
/// find.byType(SomeWidget),
/// matchesGoldenFile('someWidget.png'),
/// find.byType(MyWidget),
/// matchesGoldenFile('myWidget.png'),
/// );
/// });
/// ```
......@@ -431,7 +438,7 @@ Matcher coversSameAreaAs(Path expectedPath, { required Rect areaToCompare, int s
/// ```dart
/// Future<void> testExecutable(FutureOr<void> Function() testMain) async {
/// setUpAll(() async {
/// final fontLoader = FontLoader('SomeFont')..addFont(someFont);
/// final FontLoader fontLoader = FontLoader('SomeFont')..addFont(someFont);
/// await fontLoader.load();
/// });
///
......@@ -473,18 +480,20 @@ AsyncMatcher matchesGoldenFile(Object key, {int? version}) {
/// ## Sample code
///
/// ```dart
/// final ui.Paint paint = ui.Paint()
/// ..style = ui.PaintingStyle.stroke
/// ..strokeWidth = 1.0;
/// final ui.PictureRecorder recorder = ui.PictureRecorder();
/// final ui.Canvas pictureCanvas = ui.Canvas(recorder);
/// pictureCanvas.drawCircle(Offset.zero, 20.0, paint);
/// final ui.Picture picture = recorder.endRecording();
/// ui.Image referenceImage = picture.toImage(50, 50);
///
/// await expectLater(find.text('Save'), matchesReferenceImage(referenceImage));
/// await expectLater(image, matchesReferenceImage(referenceImage);
/// await expectLater(imageFuture, matchesReferenceImage(referenceImage));
/// testWidgets('matchesReferenceImage', (WidgetTester tester) async {
/// final ui.Paint paint = ui.Paint()
/// ..style = ui.PaintingStyle.stroke
/// ..strokeWidth = 1.0;
/// final ui.PictureRecorder recorder = ui.PictureRecorder();
/// final ui.Canvas pictureCanvas = ui.Canvas(recorder);
/// pictureCanvas.drawCircle(Offset.zero, 20.0, paint);
/// final ui.Picture picture = recorder.endRecording();
/// ui.Image referenceImage = await picture.toImage(50, 50);
///
/// await expectLater(find.text('Save'), matchesReferenceImage(referenceImage));
/// await expectLater(image, matchesReferenceImage(referenceImage));
/// await expectLater(imageFuture, matchesReferenceImage(referenceImage));
/// });
/// ```
///
/// See also:
......@@ -508,9 +517,12 @@ AsyncMatcher matchesReferenceImage(ui.Image image) {
/// ## Sample code
///
/// ```dart
/// final SemanticsHandle handle = tester.ensureSemantics();
/// expect(tester.getSemantics(find.text('hello')), matchesSemantics(label: 'hello'));
/// handle.dispose();
/// testWidgets('matchesSemantics', (WidgetTester tester) async {
/// final SemanticsHandle handle = tester.ensureSemantics();
/// // ...
/// expect(tester.getSemantics(find.text('hello')), matchesSemantics(label: 'hello'));
/// handle.dispose();
/// });
/// ```
///
/// See also:
......@@ -685,9 +697,12 @@ Matcher matchesSemantics({
/// ## Sample code
///
/// ```dart
/// final SemanticsHandle handle = tester.ensureSemantics();
/// expect(tester.getSemantics(find.text('hello')), hasSemantics(label: 'hello'));
/// handle.dispose();
/// testWidgets('containsSemantics', (WidgetTester tester) async {
/// final SemanticsHandle handle = tester.ensureSemantics();
/// // ...
/// expect(tester.getSemantics(find.text('hello')), containsSemantics(label: 'hello'));
/// handle.dispose();
/// });
/// ```
///
/// See also:
......@@ -859,9 +874,12 @@ Matcher containsSemantics({
/// ## Sample code
///
/// ```dart
/// final SemanticsHandle handle = tester.ensureSemantics();
/// await expectLater(tester, meetsGuideline(textContrastGuideline));
/// handle.dispose();
/// testWidgets('containsSemantics', (WidgetTester tester) async {
/// final SemanticsHandle handle = tester.ensureSemantics();
/// // ...
/// await expectLater(tester, meetsGuideline(textContrastGuideline));
/// handle.dispose();
/// });
/// ```
///
/// Supported accessibility guidelines:
......
......@@ -12,6 +12,10 @@ import 'finders.dart';
import 'recording_canvas.dart';
import 'test_async_utils.dart';
// Examples can assume:
// late RenderObject myRenderObject;
// late Symbol methodName;
/// Matches objects or functions that paint a display list that matches the
/// canvas calls described by the pattern.
///
......@@ -20,8 +24,8 @@ import 'test_async_utils.dart';
/// following signatures:
///
/// ```dart
/// void function(PaintingContext context, Offset offset);
/// void function(Canvas canvas);
/// void exampleOne(PaintingContext context, Offset offset) { }
/// void exampleTwo(Canvas canvas) { }
/// ```
///
/// In the case of functions that take a [PaintingContext] and an [Offset], the
......@@ -65,7 +69,9 @@ Matcher paintsExactlyCountTimes(Symbol methodName, int count) {
/// literal syntax, for example:
///
/// ```dart
/// if (methodName == #drawCircle) { ... }
/// if (methodName == #drawCircle) {
/// // ...
/// }
/// ```
typedef PaintPatternPredicate = bool Function(Symbol methodName, List<dynamic> arguments);
......
......@@ -8,15 +8,15 @@
/// ```dart
/// class A {
/// const A(this.i);
/// int i;
/// final int? i;
/// }
///
/// main () {
/// void main () {
/// // prevent prefer_const_constructors lint
/// A(nonconst(null));
///
/// // prevent prefer_const_declarations lint
/// final int $null = nonconst(null);
/// final int? $null = nonconst(null);
/// final A a = nonconst(const A(null));
/// }
/// ```
......
......@@ -35,6 +35,9 @@ class RecordedInvocation {
}
}
// Examples can assume:
// late WidgetTester tester;
/// A [Canvas] for tests that records its method calls.
///
/// This class can be used in conjunction with [TestRecordingPaintingContext]
......
......@@ -12,6 +12,9 @@ class _AsyncScope {
final Zone zone;
}
// Examples can assume:
// late WidgetTester tester;
/// Utility class for all the async APIs in the `flutter_test` library.
///
/// This class provides checking for asynchronous APIs, allowing the library to
......
......@@ -81,6 +81,9 @@ E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) {
return null;
}
// Examples can assume:
// typedef MyWidget = Placeholder;
/// Runs the [callback] inside the Flutter test environment.
///
/// Use this function for testing custom [StatelessWidget]s and
......@@ -117,7 +120,7 @@ E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) {
///
/// ```dart
/// testWidgets('MyWidget', (WidgetTester tester) async {
/// await tester.pumpWidget(MyWidget());
/// await tester.pumpWidget(const MyWidget());
/// await tester.tap(find.text('Save'));
/// expect(find.text('Success'), findsOneWidget);
/// });
......@@ -319,12 +322,13 @@ class TargetPlatformVariant extends TestVariant<TargetPlatform> {
/// }
///
/// final ValueVariant<TestScenario> variants = ValueVariant<TestScenario>(
/// <TestScenario>{value1, value2},
/// <TestScenario>{TestScenario.value1, TestScenario.value2},
/// );
///
/// testWidgets('Test handling of TestScenario', (WidgetTester tester) {
/// expect(variants.currentValue, equals(value1));
/// }, variant: variants);
/// void main() {
/// testWidgets('Test handling of TestScenario', (WidgetTester tester) async {
/// expect(variants.currentValue, equals(TestScenario.value1));
/// }, variant: variants);
/// }
/// ```
/// {@end-tool}
class ValueVariant<T> extends TestVariant<T> {
......@@ -507,7 +511,7 @@ Future<void> expectLater(
///
/// ```dart
/// testWidgets('MyWidget', (WidgetTester tester) async {
/// await tester.pumpWidget(MyWidget());
/// await tester.pumpWidget(const MyWidget());
/// await tester.tap(find.text('Save'));
/// await tester.pump(); // allow the application to handle
/// await tester.pump(const Duration(seconds: 1)); // skip past the animation
......@@ -555,7 +559,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
/// {@tool snippet}
/// ```dart
/// testWidgets('MyWidget asserts invalid bounds', (WidgetTester tester) async {
/// await tester.pumpWidget(MyWidget(-1));
/// await tester.pumpWidget(const MyWidget());
/// expect(tester.takeException(), isAssertionError); // or isNull, as appropriate.
/// });
/// ```
......
......@@ -1173,7 +1173,7 @@ class _UnsupportedDisplay implements TestDisplay {
/// // Fake the desired properties of the TestWindow. All code running
/// // within this test will perceive the following fake text scale
/// // factor as the real text scale factor of the window.
/// testBinding.window.textScaleFactorFakeValue = 2.5;
/// testBinding.window.textScaleFactorTestValue = 2.5; // ignore: deprecated_member_use
///
/// // Test code that depends on text scale factor here.
/// });
......
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