From 2e9e9453e717ee250b4a971e1becc57615a9d2da Mon Sep 17 00:00:00 2001 From: Andre Lipke Date: Thu, 5 Oct 2023 17:03:50 -0400 Subject: [PATCH] Bump version --- .circleci/config.yml | 8 +- .vscode/settings.json | 4 +- boxy/CHANGELOG.md | 4 + boxy/lib/src/boxy/custom_boxy_base.dart | 2 +- boxy/lib/src/redirect_pointer.dart | 21 +- boxy/lib/src/scale.dart | 4 + boxy/pubspec.yaml | 2 +- boxy/test/layers_test.dart | 2 +- boxy/test/mock_canvas.dart | 1914 ----------------------- boxy/test/recording_canvas.dart | 256 --- boxy/test/sliver_container_test.dart | 1 - 11 files changed, 35 insertions(+), 2183 deletions(-) delete mode 100644 boxy/test/mock_canvas.dart delete mode 100644 boxy/test/recording_canvas.dart diff --git a/.circleci/config.yml b/.circleci/config.yml index c4f994e..6533015 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ orbs: aws-s3: circleci/aws-s3@3.0 commands: - install_dart: + install_flutter: steps: - run: name: "Install Flutter" @@ -54,17 +54,13 @@ commands: jobs: linux_tests: - environment: &dart_version - DART_VERSION: "2.19.0" - DART_RELEASE: "stable" docker: - image: cimg/base:stable steps: - - install_dart + - install_flutter - test_all build_website: - environment: *dart_version docker: - image: cimg/python:3.9 steps: diff --git a/.vscode/settings.json b/.vscode/settings.json index 6bdef7b..26413d1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "dart.flutterSdkPath": "C:\\Users\\ping\\.puro\\envs\\beta\\flutter", - "dart.sdkPath": "C:\\Users\\ping\\.puro\\envs\\beta\\flutter\\bin\\cache\\dart-sdk" + "dart.flutterSdkPath": "C:\\Users\\ping\\.puro\\envs\\stable\\flutter", + "dart.sdkPath": "C:\\Users\\ping\\.puro\\envs\\stable\\flutter\\bin\\cache\\dart-sdk" } \ No newline at end of file diff --git a/boxy/CHANGELOG.md b/boxy/CHANGELOG.md index 105c26b..eacebc8 100644 --- a/boxy/CHANGELOG.md +++ b/boxy/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.1.1] +* Added the `Scale` widget which scales its child by a given factor while preserving relative size unlike `Transform`. +* Added the `RedirectPointer` widget which allows you to hit test a child widget even if it overpaints its parent. + ## [2.1.0] * Support for Flutter 3.13 diff --git a/boxy/lib/src/boxy/custom_boxy_base.dart b/boxy/lib/src/boxy/custom_boxy_base.dart index 776ccd3..7f5f428 100644 --- a/boxy/lib/src/boxy/custom_boxy_base.dart +++ b/boxy/lib/src/boxy/custom_boxy_base.dart @@ -1253,7 +1253,7 @@ class BoxyId extends ParentDataWidget { void applyParentData(RenderObject renderObject) { assert(renderObject.parentData is BaseBoxyParentData); final parentData = renderObject.parentData! as BaseBoxyParentData; - final parent = renderObject.parent! as RenderObject; + final parent = renderObject.parent!; final dynamic oldUserData = parentData.userData; if (id != parentData.id) { parentData.id = id; diff --git a/boxy/lib/src/redirect_pointer.dart b/boxy/lib/src/redirect_pointer.dart index ac1f502..74f6846 100644 --- a/boxy/lib/src/redirect_pointer.dart +++ b/boxy/lib/src/redirect_pointer.dart @@ -1,6 +1,25 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +/// Redirects pointer events to widgets anywhere else in the tree. +/// +/// This is useful for widgets that overflow their parent such as from a +/// [Transform] and would otherwise not receive pointer events. +/// +/// To use this widget, give the child you would like to receive pointer events +/// a [GlobalKey] and pass it to the [above] or [below] parameter. +/// +/// Note that the RedirectPointer widget needs to encompass the entire widget +/// that should receive pointer events, we recommend wrapping the body of the +/// [Scaffold] so that it can receive pointer events for the whole screen. +/// +/// You may also want to wrap the targets in an [IgnorePointer] so they aren't +/// hit tested more than once. +/// +/// Hit testing is performed in the following order: +/// 1. The [above] widgets in reverse. +/// 2. The child. +/// 3. The [below] widgets in reverse. class RedirectPointer extends SingleChildRenderObjectWidget { const RedirectPointer({ super.key, diff --git a/boxy/lib/src/scale.dart b/boxy/lib/src/scale.dart index 5beb743..a092db8 100644 --- a/boxy/lib/src/scale.dart +++ b/boxy/lib/src/scale.dart @@ -2,6 +2,10 @@ import 'package:flutter/material.dart'; import '../boxy.dart'; +/// Scales its child by a given factor. +/// +/// Unlike [Transform], this widget will pass adjusted constraints to its child +/// so that it can be laid out at the correct size without overflowing. class Scale extends StatelessWidget { const Scale({ super.key, diff --git a/boxy/pubspec.yaml b/boxy/pubspec.yaml index 7dfc6c4..c4e2082 100644 --- a/boxy/pubspec.yaml +++ b/boxy/pubspec.yaml @@ -1,6 +1,6 @@ name: boxy description: Overcome limitations of built-in layouts, advanced flex, custom multi-child layouts, slivers, and more! -version: 2.1.0 +version: 2.1.1 homepage: https://github.com/pingbird/boxy documentation: https://boxy.wiki diff --git a/boxy/test/layers_test.dart b/boxy/test/layers_test.dart index 93af3ec..a93ad88 100644 --- a/boxy/test/layers_test.dart +++ b/boxy/test/layers_test.dart @@ -47,7 +47,7 @@ void main() { OpacityLayer getLayer() { final rootLayer = // ignore: invalid_use_of_protected_member - RendererBinding.instance.renderView.layer! as TransformLayer; + RendererBinding.instance.renderViews.single.layer! as TransformLayer; final pictureLayer = rootLayer.firstChild! as PictureLayer; return pictureLayer.nextSibling! as OpacityLayer; } diff --git a/boxy/test/mock_canvas.dart b/boxy/test/mock_canvas.dart deleted file mode 100644 index 61c2277..0000000 --- a/boxy/test/mock_canvas.dart +++ /dev/null @@ -1,1914 +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. - -// ignore_for_file: only_throw_errors - -import 'dart:ui' as ui show Image, Paragraph; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'recording_canvas.dart'; - -/// Matches objects or functions that paint a display list that matches the -/// canvas calls described by the pattern. -/// -/// Specifically, this can be applied to [RenderObject]s, [Finder]s that -/// correspond to a single [RenderObject], and functions that have either of the -/// following signatures: -/// -/// ```dart -/// void function(PaintingContext context, Offset offset); -/// void function(Canvas canvas); -/// ``` -/// -/// In the case of functions that take a [PaintingContext] and an [Offset], the -/// [paints] matcher will always pass a zero offset. -/// -/// To specify the pattern, call the methods on the returned object. For example: -/// -/// ```dart -/// expect(myRenderObject, paints..circle(radius: 10.0)..circle(radius: 20.0)); -/// ``` -/// -/// This particular pattern would verify that the render object `myRenderObject` -/// paints, among other things, two circles of radius 10.0 and 20.0 (in that -/// order). -/// -/// See [PaintPattern] for a discussion of the semantics of paint patterns. -/// -/// To match something which paints nothing, see [paintsNothing]. -/// -/// To match something which asserts instead of painting, see [paintsAssertion]. -PaintPattern get paints => _TestRecordingCanvasPatternMatcher(); - -/// Matches objects or functions that does not paint anything on the canvas. -Matcher get paintsNothing => _TestRecordingCanvasPaintsNothingMatcher(); - -/// Matches objects or functions that assert when they try to paint. -Matcher get paintsAssertion => _TestRecordingCanvasPaintsAssertionMatcher(); - -/// Matches objects or functions that draw `methodName` exactly `count` number of times. -Matcher paintsExactlyCountTimes(Symbol methodName, int count) { - return _TestRecordingCanvasPaintsCountMatcher(methodName, count); -} - -/// Signature for the [PaintPattern.something] and [PaintPattern.everything] -/// predicate argument. -/// -/// Used by the [paints] matcher. -/// -/// The `methodName` argument is a [Symbol], and can be compared with the symbol -/// literal syntax, for example: -/// -/// ```dart -/// if (methodName == #drawCircle) { ... } -/// ``` -typedef PaintPatternPredicate = bool Function( - Symbol methodName, List arguments); - -/// The signature of [RenderObject.paint] functions. -typedef _ContextPainterFunction = void Function( - PaintingContext context, Offset offset); - -/// The signature of functions that paint directly on a canvas. -typedef _CanvasPainterFunction = void Function(Canvas canvas); - -/// Builder interface for patterns used to match display lists (canvas calls). -/// -/// The [paints] matcher returns a [PaintPattern] so that you can build the -/// pattern in the [expect] call. -/// -/// Patterns are subset matches, meaning that any calls not described by the -/// pattern are ignored. This allows, for instance, transforms to be skipped. -abstract class PaintPattern { - /// Indicates that a transform is expected next. - /// - /// Calls are skipped until a call to [Canvas.transform] is found. The call's - /// arguments are compared to those provided here. If any fail to match, or if - /// no call to [Canvas.transform] is found, then the matcher fails. - /// - /// Dynamic so matchers can be more easily passed in. - /// - /// The `matrix4` argument is dynamic so it can be either a [Matcher], or a - /// [Float64List] of [double]s. If it is a [Float64List] of [double]s then - /// each value in the matrix must match in the expected matrix. A deep - /// matching [Matcher] such as [equals] can be used to test each value in the - /// matrix with utilities such as [moreOrLessEquals]. - void transform({dynamic matrix4}); - - /// Indicates that a translation transform is expected next. - /// - /// Calls are skipped until a call to [Canvas.translate] is found. The call's - /// arguments are compared to those provided here. If any fail to match, or if - /// no call to [Canvas.translate] is found, then the matcher fails. - void translate({double? x, double? y}); - - /// Indicates that a scale transform is expected next. - /// - /// Calls are skipped until a call to [Canvas.scale] is found. The call's - /// arguments are compared to those provided here. If any fail to match, or if - /// no call to [Canvas.scale] is found, then the matcher fails. - void scale({double? x, double? y}); - - /// Indicates that a rotate transform is expected next. - /// - /// Calls are skipped until a call to [Canvas.rotate] is found. If the `angle` - /// argument is provided here, the call's argument is compared to it. If that - /// fails to match, or if no call to [Canvas.rotate] is found, then the - /// matcher fails. - void rotate({double? angle}); - - /// Indicates that a save is expected next. - /// - /// Calls are skipped until a call to [Canvas.save] is found. If none is - /// found, the matcher fails. - /// - /// See also: - /// - /// * [restore], which indicates that a restore is expected next. - /// * [saveRestore], which indicates that a matching pair of save/restore - /// calls is expected next. - void save(); - - /// Indicates that a restore is expected next. - /// - /// Calls are skipped until a call to [Canvas.restore] is found. If none is - /// found, the matcher fails. - /// - /// See also: - /// - /// * [save], which indicates that a save is expected next. - /// * [saveRestore], which indicates that a matching pair of save/restore - /// calls is expected next. - void restore(); - - /// Indicates that a matching pair of save/restore calls is expected next. - /// - /// Calls are skipped until a call to [Canvas.save] is found, then, calls are - /// skipped until the matching [Canvas.restore] call is found. If no matching - /// pair of calls could be found, the matcher fails. - /// - /// See also: - /// - /// * [save], which indicates that a save is expected next. - /// * [restore], which indicates that a restore is expected next. - void saveRestore(); - - /// Indicates that a rectangular clip is expected next. - /// - /// The next rectangular clip is examined. Any arguments that are passed to - /// this method are compared to the actual [Canvas.clipRect] call's argument - /// and any mismatches result in failure. - /// - /// If no call to [Canvas.clipRect] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.clipRect] call are ignored. - void clipRect({Rect? rect}); - - /// Indicates that a path clip is expected next. - /// - /// The next path clip is examined. - /// The path that is passed to the actual [Canvas.clipPath] call is matched - /// using [pathMatcher]. - /// - /// If no call to [Canvas.clipPath] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.clipPath] call are ignored. - void clipPath({Matcher? pathMatcher}); - - /// Indicates that a rectangle is expected next. - /// - /// The next rectangle is examined. Any arguments that are passed to this - /// method are compared to the actual [Canvas.drawRect] call's arguments - /// and any mismatches result in failure. - /// - /// If no call to [Canvas.drawRect] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawRect] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void rect( - {Rect? rect, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}); - - /// Indicates that a rounded rectangle clip is expected next. - /// - /// The next rounded rectangle clip is examined. Any arguments that are passed - /// to this method are compared to the actual [Canvas.clipRRect] call's - /// argument and any mismatches result in failure. - /// - /// If no call to [Canvas.clipRRect] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.clipRRect] call are ignored. - void clipRRect({RRect? rrect}); - - /// Indicates that a rounded rectangle is expected next. - /// - /// The next rounded rectangle is examined. Any arguments that are passed to - /// this method are compared to the actual [Canvas.drawRRect] call's arguments - /// and any mismatches result in failure. - /// - /// If no call to [Canvas.drawRRect] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawRRect] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void rrect( - {RRect? rrect, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}); - - /// Indicates that a rounded rectangle outline is expected next. - /// - /// The next call to [Canvas.drawRRect] is examined. Any arguments that are - /// passed to this method are compared to the actual [Canvas.drawRRect] call's - /// arguments and any mismatches result in failure. - /// - /// If no call to [Canvas.drawRRect] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawRRect] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void drrect( - {RRect? outer, - RRect? inner, - Color? color, - double strokeWidth, - bool hasMaskFilter, - PaintingStyle style}); - - /// Indicates that a circle is expected next. - /// - /// The next circle is examined. Any arguments that are passed to this method - /// are compared to the actual [Canvas.drawCircle] call's arguments and any - /// mismatches result in failure. - /// - /// If no call to [Canvas.drawCircle] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawCircle] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void circle( - {double? x, - double? y, - double? radius, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}); - - /// Indicates that a path is expected next. - /// - /// The next path is examined. Any arguments that are passed to this method - /// are compared to the actual [Canvas.drawPath] call's `paint` argument, and - /// any mismatches result in failure. - /// - /// To introspect the Path object (as it stands after the painting has - /// completed), the `includes` and `excludes` arguments can be provided to - /// specify points that should be considered inside or outside the path - /// (respectively). - /// - /// If no call to [Canvas.drawPath] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawPath] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void path( - {Iterable? includes, - Iterable? excludes, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}); - - /// Indicates that a line is expected next. - /// - /// The next line is examined. Any arguments that are passed to this method - /// are compared to the actual [Canvas.drawLine] call's `p1`, `p2`, and - /// `paint` arguments, and any mismatches result in failure. - /// - /// If no call to [Canvas.drawLine] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawLine] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void line( - {Offset? p1, - Offset? p2, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}); - - /// Indicates that an arc is expected next. - /// - /// The next arc is examined. Any arguments that are passed to this method - /// are compared to the actual [Canvas.drawArc] call's `paint` argument, and - /// any mismatches result in failure. - /// - /// If no call to [Canvas.drawArc] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawArc] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void arc( - {Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}); - - /// Indicates that a paragraph is expected next. - /// - /// Calls are skipped until a call to [Canvas.drawParagraph] is found. Any - /// arguments that are passed to this method are compared to the actual - /// [Canvas.drawParagraph] call's argument, and any mismatches result in failure. - /// - /// The `offset` argument can be either an [Offset] or a [Matcher]. If it is - /// an [Offset] then the actual value must match the expected offset - /// precisely. If it is a [Matcher] then the comparison is made according to - /// the semantics of the [Matcher]. For example, [within] can be used to - /// assert that the actual offset is within a given distance from the expected - /// offset. - /// - /// If no call to [Canvas.drawParagraph] was made, then this results in failure. - void paragraph({ui.Paragraph? paragraph, dynamic offset}); - - /// Indicates that a shadow is expected next. - /// - /// The next shadow is examined. Any arguments that are passed to this method - /// are compared to the actual [Canvas.drawShadow] call's `paint` argument, - /// and any mismatches result in failure. - /// - /// In tests, shadows from framework features such as [BoxShadow] or - /// [Material] are disabled by default, and thus this predicate would not - /// match. The [debugDisableShadows] flag controls this. - /// - /// To introspect the Path object (as it stands after the painting has - /// completed), the `includes` and `excludes` arguments can be provided to - /// specify points that should be considered inside or outside the path - /// (respectively). - /// - /// If no call to [Canvas.drawShadow] was made, then this results in failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawShadow] call are ignored. - void shadow( - {Iterable? includes, - Iterable? excludes, - Color? color, - double? elevation, - bool? transparentOccluder}); - - /// Indicates that an image is expected next. - /// - /// The next call to [Canvas.drawImage] is examined, and its arguments - /// compared to those passed to _this_ method. - /// - /// If no call to [Canvas.drawImage] was made, then this results in - /// failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawImage] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void image( - {ui.Image? image, - double? x, - double? y, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}); - - /// Indicates that an image subsection is expected next. - /// - /// The next call to [Canvas.drawImageRect] is examined, and its arguments - /// compared to those passed to _this_ method. - /// - /// If no call to [Canvas.drawImageRect] was made, then this results in - /// failure. - /// - /// Any calls made between the last matched call (if any) and the - /// [Canvas.drawImageRect] call are ignored. - /// - /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, - /// `style`) are compared against the state of the [Paint] object after the - /// painting has completed, not at the time of the call. If the same [Paint] - /// object is reused multiple times, then this may not match the actual - /// arguments as they were seen by the method. - void drawImageRect( - {ui.Image? image, - Rect? source, - Rect? destination, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}); - - /// Provides a custom matcher. - /// - /// Each method call after the last matched call (if any) will be passed to - /// the given predicate, along with the values of its (positional) arguments. - /// - /// For each one, the predicate must either return a boolean or throw a [String]. - /// - /// If the predicate returns true, the call is considered a successful match - /// and the next step in the pattern is examined. If this was the last step, - /// then any calls that were not yet matched are ignored and the [paints] - /// [Matcher] is considered a success. - /// - /// If the predicate returns false, then the call is considered uninteresting - /// and the predicate will be called again for the next [Canvas] call that was - /// made by the [RenderObject] under test. If this was the last call, then the - /// [paints] [Matcher] is considered to have failed. - /// - /// If the predicate throws a [String], then the [paints] [Matcher] is - /// considered to have failed. The thrown string is used in the message - /// displayed from the test framework and should be complete sentence - /// describing the problem. - void something(PaintPatternPredicate predicate); - - /// Provides a custom matcher. - /// - /// Each method call after the last matched call (if any) will be passed to - /// the given predicate, along with the values of its (positional) arguments. - /// - /// For each one, the predicate must either return a boolean or throw a [String]. - /// - /// The predicate will be applied to each [Canvas] call until it returns false - /// or all of the method calls have been tested. - /// - /// If the predicate returns false, then the [paints] [Matcher] is considered - /// to have failed. If all calls are tested without failing, then the [paints] - /// [Matcher] is considered a success. - /// - /// If the predicate throws a [String], then the [paints] [Matcher] is - /// considered to have failed. The thrown string is used in the message - /// displayed from the test framework and should be complete sentence - /// describing the problem. - void everything(PaintPatternPredicate predicate); -} - -/// Matches a [Path] that contains (as defined by [Path.contains]) the given -/// `includes` points and does not contain the given `excludes` points. -Matcher isPathThat({ - Iterable includes = const [], - Iterable excludes = const [], -}) { - return _PathMatcher(includes.toList(), excludes.toList()); -} - -class _PathMatcher extends Matcher { - _PathMatcher(this.includes, this.excludes); - - List includes; - List excludes; - - @override - bool matches(Object? object, Map matchState) { - if (object is! Path) { - matchState[this] = 'The given object ($object) was not a Path.'; - return false; - } - final Path path = object; - final List errors = [ - for (final Offset offset in includes) - if (!path.contains(offset)) - 'Offset $offset should be inside the path, but is not.', - for (final Offset offset in excludes) - if (path.contains(offset)) - 'Offset $offset should be outside the path, but is not.', - ]; - if (errors.isEmpty) { - return true; - } - matchState[this] = - 'Not all the given points were inside or outside the path as expected:\n ${errors.join("\n ")}'; - return false; - } - - @override - Description describe(Description description) { - String points(List list) { - final int count = list.length; - if (count == 1) { - return 'one particular point'; - } - return '$count particular points'; - } - - return description.add( - 'A Path that contains ${points(includes)} but does not contain ${points(excludes)}.'); - } - - @override - Description describeMismatch( - dynamic item, - Description description, - Map matchState, - bool verbose, - ) { - return description.add(matchState[this] as String); - } -} - -class _MismatchedCall { - const _MismatchedCall(this.message, this.callIntroduction, this.call); - final String message; - final String callIntroduction; - final RecordedInvocation call; -} - -bool _evaluatePainter(Object? object, Canvas canvas, PaintingContext context) { - if (object is _ContextPainterFunction) { - final _ContextPainterFunction function = object; - function(context, Offset.zero); - } else if (object is _CanvasPainterFunction) { - final _CanvasPainterFunction function = object; - function(canvas); - } else { - if (object is Finder) { - TestAsyncUtils.guardSync(); - final Finder finder = object; - object = finder.evaluate().single.renderObject; - } - if (object is RenderObject) { - final RenderObject renderObject = object; - renderObject.paint(context, Offset.zero); - } else { - return false; - } - } - return true; -} - -abstract class _TestRecordingCanvasMatcher extends Matcher { - @override - bool matches(Object? object, Map matchState) { - final TestRecordingCanvas canvas = TestRecordingCanvas(); - final TestRecordingPaintingContext context = - TestRecordingPaintingContext(canvas); - final StringBuffer description = StringBuffer(); - String prefixMessage = 'unexpectedly failed.'; - bool result = false; - try { - if (!_evaluatePainter(object, canvas, context)) { - matchState[this] = - 'was not one of the supported objects for the "paints" matcher.'; - return false; - } - result = _evaluatePredicates(canvas.invocations, description); - if (!result) { - prefixMessage = 'did not match the pattern.'; - } - } catch (error, stack) { - prefixMessage = 'threw the following exception:'; - description.writeln(error.toString()); - description.write(stack.toString()); - result = false; - } - if (!result) { - if (canvas.invocations.isNotEmpty) { - description.write('The complete display list was:'); - for (final RecordedInvocation call in canvas.invocations) { - description.write('\n * $call'); - } - } - matchState[this] = '$prefixMessage\n$description'; - } - return result; - } - - bool _evaluatePredicates( - Iterable calls, StringBuffer description); - - @override - Description describeMismatch( - dynamic item, - Description description, - Map matchState, - bool verbose, - ) { - return description.add(matchState[this] as String); - } -} - -class _TestRecordingCanvasPaintsCountMatcher - extends _TestRecordingCanvasMatcher { - _TestRecordingCanvasPaintsCountMatcher(Symbol methodName, int count) - : _methodName = methodName, - _count = count; - - final Symbol _methodName; - final int _count; - - @override - Description describe(Description description) { - return description - .add('Object or closure painting $_methodName exactly $_count times'); - } - - @override - bool _evaluatePredicates( - Iterable calls, StringBuffer description) { - int count = 0; - for (final RecordedInvocation call in calls) { - if (call.invocation.isMethod && - call.invocation.memberName == _methodName) { - count++; - } - } - if (count != _count) { - description.write( - 'It painted $_methodName $count times instead of $_count times.'); - } - return count == _count; - } -} - -class _TestRecordingCanvasPaintsNothingMatcher - extends _TestRecordingCanvasMatcher { - @override - Description describe(Description description) { - return description.add('An object or closure that paints nothing.'); - } - - @override - bool _evaluatePredicates( - Iterable calls, StringBuffer description) { - final Iterable paintingCalls = - _filterCanvasCalls(calls); - if (paintingCalls.isEmpty) { - return true; - } - description.write( - 'painted something, the first call having the following stack:\n' - '${paintingCalls.first.stackToString(indent: " ")}\n', - ); - return false; - } - - static const List _nonPaintingOperations = [ - #save, - #restore, - ]; - - // Filters out canvas calls that are not painting anything. - static Iterable _filterCanvasCalls( - Iterable canvasCalls) { - return canvasCalls.where( - (RecordedInvocation canvasCall) => - !_nonPaintingOperations.contains(canvasCall.invocation.memberName), - ); - } -} - -class _TestRecordingCanvasPaintsAssertionMatcher extends Matcher { - @override - bool matches(Object? object, Map matchState) { - final TestRecordingCanvas canvas = TestRecordingCanvas(); - final TestRecordingPaintingContext context = - TestRecordingPaintingContext(canvas); - final StringBuffer description = StringBuffer(); - String prefixMessage = 'unexpectedly failed.'; - bool result = false; - try { - if (!_evaluatePainter(object, canvas, context)) { - matchState[this] = - 'was not one of the supported objects for the "paints" matcher.'; - return false; - } - prefixMessage = 'did not assert.'; - } on AssertionError { - result = true; - } catch (error, stack) { - prefixMessage = 'threw the following exception:'; - description.writeln(error.toString()); - description.write(stack.toString()); - result = false; - } - if (!result) { - if (canvas.invocations.isNotEmpty) { - description.write('The complete display list was:'); - for (final RecordedInvocation call in canvas.invocations) { - description.write('\n * $call'); - } - } - matchState[this] = '$prefixMessage\n$description'; - } - return result; - } - - @override - Description describe(Description description) { - return description - .add('An object or closure that asserts when it tries to paint.'); - } - - @override - Description describeMismatch( - dynamic item, - Description description, - Map matchState, - bool verbose, - ) { - return description.add(matchState[this] as String); - } -} - -class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher - implements PaintPattern { - final List<_PaintPredicate> _predicates = <_PaintPredicate>[]; - - @override - void transform({dynamic matrix4}) { - _predicates.add(_FunctionPaintPredicate(#transform, [matrix4])); - } - - @override - void translate({double? x, double? y}) { - _predicates.add(_FunctionPaintPredicate(#translate, [x, y])); - } - - @override - void scale({double? x, double? y}) { - _predicates.add(_FunctionPaintPredicate(#scale, [x, y])); - } - - @override - void rotate({double? angle}) { - _predicates.add(_FunctionPaintPredicate(#rotate, [angle])); - } - - @override - void save() { - _predicates.add(_FunctionPaintPredicate(#save, [])); - } - - @override - void restore() { - _predicates.add(_FunctionPaintPredicate(#restore, [])); - } - - @override - void saveRestore() { - _predicates.add(_SaveRestorePairPaintPredicate()); - } - - @override - void clipRect({Rect? rect}) { - _predicates.add(_FunctionPaintPredicate(#clipRect, [rect])); - } - - @override - void clipPath({Matcher? pathMatcher}) { - _predicates.add(_FunctionPaintPredicate(#clipPath, [pathMatcher])); - } - - @override - void rect( - {Rect? rect, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_RectPaintPredicate( - rect: rect, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void clipRRect({RRect? rrect}) { - _predicates.add(_FunctionPaintPredicate(#clipRRect, [rrect])); - } - - @override - void rrect( - {RRect? rrect, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_RRectPaintPredicate( - rrect: rrect, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void drrect( - {RRect? outer, - RRect? inner, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_DRRectPaintPredicate( - outer: outer, - inner: inner, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void circle( - {double? x, - double? y, - double? radius, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_CirclePaintPredicate( - x: x, - y: y, - radius: radius, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void path( - {Iterable? includes, - Iterable? excludes, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_PathPaintPredicate( - includes: includes, - excludes: excludes, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void line( - {Offset? p1, - Offset? p2, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_LinePaintPredicate( - p1: p1, - p2: p2, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void arc( - {Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_ArcPaintPredicate( - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void paragraph({ui.Paragraph? paragraph, dynamic offset}) { - _predicates.add( - _FunctionPaintPredicate(#drawParagraph, [paragraph, offset])); - } - - @override - void shadow( - {Iterable? includes, - Iterable? excludes, - Color? color, - double? elevation, - bool? transparentOccluder}) { - _predicates.add(_ShadowPredicate( - includes: includes, - excludes: excludes, - color: color, - elevation: elevation, - transparentOccluder: transparentOccluder)); - } - - @override - void image( - {ui.Image? image, - double? x, - double? y, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_DrawImagePaintPredicate( - image: image, - x: x, - y: y, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void drawImageRect( - {ui.Image? image, - Rect? source, - Rect? destination, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) { - _predicates.add(_DrawImageRectPaintPredicate( - image: image, - source: source, - destination: destination, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style)); - } - - @override - void something(PaintPatternPredicate predicate) { - _predicates.add(_SomethingPaintPredicate(predicate)); - } - - @override - void everything(PaintPatternPredicate predicate) { - _predicates.add(_EverythingPaintPredicate(predicate)); - } - - @override - Description describe(Description description) { - if (_predicates.isEmpty) { - return description.add('An object or closure and a paint pattern.'); - } - description.add('Object or closure painting:\n'); - return description.addAll( - '', - '\n', - '', - _predicates - .map((_PaintPredicate predicate) => predicate.toString()), - ); - } - - @override - bool _evaluatePredicates( - Iterable calls, StringBuffer description) { - if (calls.isEmpty) { - description.writeln('It painted nothing.'); - return false; - } - if (_predicates.isEmpty) { - description.writeln( - 'It painted something, but you must now add a pattern to the paints matcher ' - 'in the test to verify that it matches the important parts of the following.', - ); - return false; - } - final Iterator<_PaintPredicate> predicate = _predicates.iterator; - final Iterator call = calls.iterator..moveNext(); - try { - while (predicate.moveNext()) { - predicate.current.match(call); - } - // We allow painting more than expected. - } on _MismatchedCall catch (data) { - description.writeln(data.message); - description.writeln(data.callIntroduction); - description.writeln(data.call.stackToString(indent: ' ')); - return false; - } on String catch (s) { - description.writeln(s); - try { - description.write( - 'The stack of the offending call was:\n${call.current.stackToString(indent: " ")}\n'); - } on TypeError catch (_) { - // All calls have been evaluated - } - return false; - } - return true; - } -} - -abstract class _PaintPredicate { - void match(Iterator call); - - @protected - void checkMethod(Iterator call, Symbol symbol) { - int others = 0; - final RecordedInvocation firstCall = call.current; - while (!call.current.invocation.isMethod || - call.current.invocation.memberName != symbol) { - others += 1; - if (!call.moveNext()) { - throw _MismatchedCall( - 'It called $others other method${others == 1 ? "" : "s"} on the canvas, ' - 'the first of which was $firstCall, but did not ' - 'call ${_symbolName(symbol)}() at the time where $this was expected.', - 'The first method that was called when the call to ${_symbolName(symbol)}() ' - 'was expected, $firstCall, was called with the following stack:', - firstCall, - ); - } - } - } - - @override - String toString() { - throw FlutterError('$runtimeType does not implement toString.'); - } -} - -abstract class _DrawCommandPaintPredicate extends _PaintPredicate { - _DrawCommandPaintPredicate( - this.symbol, - this.name, - this.argumentCount, - this.paintArgumentIndex, { - this.color, - this.strokeWidth, - this.hasMaskFilter, - this.style, - }); - - final Symbol symbol; - final String name; - final int argumentCount; - final int paintArgumentIndex; - final Color? color; - final double? strokeWidth; - final bool? hasMaskFilter; - final PaintingStyle? style; - - String get methodName => _symbolName(symbol); - - @override - void match(Iterator call) { - checkMethod(call, symbol); - final int actualArgumentCount = - call.current.invocation.positionalArguments.length; - if (actualArgumentCount != argumentCount) { - throw 'It called $methodName with $actualArgumentCount argument${actualArgumentCount == 1 ? "" : "s"}; expected $argumentCount.'; - } - verifyArguments(call.current.invocation.positionalArguments); - call.moveNext(); - } - - @protected - @mustCallSuper - void verifyArguments(List arguments) { - final Paint paintArgument = arguments[paintArgumentIndex] as Paint; - if (color != null && paintArgument.color != color) { - throw 'It called $methodName with a paint whose color, ${paintArgument.color}, was not exactly the expected color ($color).'; - } - if (strokeWidth != null && paintArgument.strokeWidth != strokeWidth) { - throw 'It called $methodName with a paint whose strokeWidth, ${paintArgument.strokeWidth}, was not exactly the expected strokeWidth ($strokeWidth).'; - } - if (hasMaskFilter != null && - (paintArgument.maskFilter != null) != hasMaskFilter) { - if (hasMaskFilter!) { - throw 'It called $methodName with a paint that did not have a mask filter, despite expecting one.'; - } else { - throw 'It called $methodName with a paint that did have a mask filter, despite not expecting one.'; - } - } - if (style != null && paintArgument.style != style) { - throw 'It called $methodName with a paint whose style, ${paintArgument.style}, was not exactly the expected style ($style).'; - } - } - - @override - String toString() { - final List description = []; - debugFillDescription(description); - String result = name; - if (description.isNotEmpty) { - result += ' with ${description.join(", ")}'; - } - return result; - } - - @protected - @mustCallSuper - void debugFillDescription(List description) { - if (color != null) { - description.add('$color'); - } - if (strokeWidth != null) { - description.add('strokeWidth: $strokeWidth'); - } - if (hasMaskFilter != null) { - description.add(hasMaskFilter! ? 'a mask filter' : 'no mask filter'); - } - if (style != null) { - description.add('$style'); - } - } -} - -class _OneParameterPaintPredicate extends _DrawCommandPaintPredicate { - _OneParameterPaintPredicate( - Symbol symbol, - String name, { - required this.expected, - required Color? color, - required double? strokeWidth, - required bool? hasMaskFilter, - required PaintingStyle? style, - }) : super( - symbol, - name, - 2, - 1, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); - - final T? expected; - - @override - void verifyArguments(List arguments) { - super.verifyArguments(arguments); - final T actual = arguments[0] as T; - if (expected != null && actual != expected) { - throw 'It called $methodName with $T, $actual, which was not exactly the expected $T ($expected).'; - } - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (expected != null) { - if (expected.toString().contains(T.toString())) { - description.add('$expected'); - } else { - description.add('$T: $expected'); - } - } - } -} - -class _TwoParameterPaintPredicate extends _DrawCommandPaintPredicate { - _TwoParameterPaintPredicate( - Symbol symbol, - String name, { - required this.expected1, - required this.expected2, - required Color? color, - required double? strokeWidth, - required bool? hasMaskFilter, - required PaintingStyle? style, - }) : super( - symbol, - name, - 3, - 2, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); - - final T1? expected1; - - final T2? expected2; - - @override - void verifyArguments(List arguments) { - super.verifyArguments(arguments); - final T1 actual1 = arguments[0] as T1; - if (expected1 != null && actual1 != expected1) { - throw 'It called $methodName with its first argument (a $T1), $actual1, which was not exactly the expected $T1 ($expected1).'; - } - final T2 actual2 = arguments[1] as T2; - if (expected2 != null && actual2 != expected2) { - throw 'It called $methodName with its second argument (a $T2), $actual2, which was not exactly the expected $T2 ($expected2).'; - } - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (expected1 != null) { - if (expected1.toString().contains(T1.toString())) { - description.add('$expected1'); - } else { - description.add('$T1: $expected1'); - } - } - if (expected2 != null) { - if (expected2.toString().contains(T2.toString())) { - description.add('$expected2'); - } else { - description.add('$T2: $expected2'); - } - } - } -} - -class _RectPaintPredicate extends _OneParameterPaintPredicate { - _RectPaintPredicate( - {Rect? rect, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawRect, - 'a rectangle', - expected: rect, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); -} - -class _RRectPaintPredicate extends _DrawCommandPaintPredicate { - _RRectPaintPredicate( - {this.rrect, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawRRect, - 'a rounded rectangle', - 2, - 1, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); - - final RRect? rrect; - - @override - void verifyArguments(List arguments) { - super.verifyArguments(arguments); - const double eps = .0001; - final RRect actual = arguments[0] as RRect; - if (rrect != null && - ((actual.left - rrect!.left).abs() > eps || - (actual.right - rrect!.right).abs() > eps || - (actual.top - rrect!.top).abs() > eps || - (actual.bottom - rrect!.bottom).abs() > eps || - (actual.blRadiusX - rrect!.blRadiusX).abs() > eps || - (actual.blRadiusY - rrect!.blRadiusY).abs() > eps || - (actual.brRadiusX - rrect!.brRadiusX).abs() > eps || - (actual.brRadiusY - rrect!.brRadiusY).abs() > eps || - (actual.tlRadiusX - rrect!.tlRadiusX).abs() > eps || - (actual.tlRadiusY - rrect!.tlRadiusY).abs() > eps || - (actual.trRadiusX - rrect!.trRadiusX).abs() > eps || - (actual.trRadiusY - rrect!.trRadiusY).abs() > eps)) { - throw 'It called $methodName with RRect, $actual, which was not exactly the expected RRect ($rrect).'; - } - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (rrect != null) { - description.add('RRect: $rrect'); - } - } -} - -class _DRRectPaintPredicate extends _TwoParameterPaintPredicate { - _DRRectPaintPredicate( - {RRect? inner, - RRect? outer, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawDRRect, - 'a rounded rectangle outline', - expected1: outer, - expected2: inner, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); -} - -class _CirclePaintPredicate extends _DrawCommandPaintPredicate { - _CirclePaintPredicate( - {this.x, - this.y, - this.radius, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawCircle, - 'a circle', - 3, - 2, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); - - final double? x; - final double? y; - final double? radius; - - @override - void verifyArguments(List arguments) { - super.verifyArguments(arguments); - final Offset pointArgument = arguments[0] as Offset; - if (x != null && y != null) { - final Offset point = Offset(x!, y!); - if (point != pointArgument) { - throw 'It called $methodName with a center coordinate, $pointArgument, which was not exactly the expected coordinate ($point).'; - } - } else { - if (x != null && pointArgument.dx != x) { - throw 'It called $methodName with a center coordinate, $pointArgument, whose x-coordinate not exactly the expected coordinate (${x!.toStringAsFixed(1)}).'; - } - if (y != null && pointArgument.dy != y) { - throw 'It called $methodName with a center coordinate, $pointArgument, whose y-coordinate not exactly the expected coordinate (${y!.toStringAsFixed(1)}).'; - } - } - final double radiusArgument = arguments[1] as double; - if (radius != null && radiusArgument != radius) { - throw 'It called $methodName with radius, ${radiusArgument.toStringAsFixed(1)}, which was not exactly the expected radius (${radius!.toStringAsFixed(1)}).'; - } - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (x != null && y != null) { - description.add('point ${Offset(x!, y!)}'); - } else { - if (x != null) { - description.add('x-coordinate ${x!.toStringAsFixed(1)}'); - } - if (y != null) { - description.add('y-coordinate ${y!.toStringAsFixed(1)}'); - } - } - if (radius != null) { - description.add('radius ${radius!.toStringAsFixed(1)}'); - } - } -} - -class _PathPaintPredicate extends _DrawCommandPaintPredicate { - _PathPaintPredicate( - {this.includes, - this.excludes, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawPath, - 'a path', - 2, - 1, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); - - final Iterable? includes; - final Iterable? excludes; - - @override - void verifyArguments(List arguments) { - super.verifyArguments(arguments); - final Path pathArgument = arguments[0] as Path; - if (includes != null) { - for (final Offset offset in includes!) { - if (!pathArgument.contains(offset)) { - throw 'It called $methodName with a path that unexpectedly did not contain $offset.'; - } - } - } - if (excludes != null) { - for (final Offset offset in excludes!) { - if (pathArgument.contains(offset)) { - throw 'It called $methodName with a path that unexpectedly contained $offset.'; - } - } - } - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (includes != null && excludes != null) { - description.add('that contains $includes and does not contain $excludes'); - } else if (includes != null) { - description.add('that contains $includes'); - } else if (excludes != null) { - description.add('that does not contain $excludes'); - } - } -} - -// TODO(ianh): add arguments to test the length, angle, that kind of thing -class _LinePaintPredicate extends _DrawCommandPaintPredicate { - _LinePaintPredicate( - {this.p1, - this.p2, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawLine, - 'a line', - 3, - 2, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); - - final Offset? p1; - final Offset? p2; - - @override - void verifyArguments(List arguments) { - super.verifyArguments(arguments); // Checks the 3rd argument, a Paint - if (arguments.length != 3) { - throw 'It called $methodName with ${arguments.length} arguments; expected 3.'; - } - final Offset p1Argument = arguments[0] as Offset; - final Offset p2Argument = arguments[1] as Offset; - if (p1 != null && p1Argument != p1) { - throw 'It called $methodName with p1 endpoint, $p1Argument, which was not exactly the expected endpoint ($p1).'; - } - if (p2 != null && p2Argument != p2) { - throw 'It called $methodName with p2 endpoint, $p2Argument, which was not exactly the expected endpoint ($p2).'; - } - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (p1 != null) { - description.add('end point p1: $p1'); - } - if (p2 != null) { - description.add('end point p2: $p2'); - } - } -} - -class _ArcPaintPredicate extends _DrawCommandPaintPredicate { - _ArcPaintPredicate( - {Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawArc, - 'an arc', - 5, - 4, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); -} - -class _ShadowPredicate extends _PaintPredicate { - _ShadowPredicate( - {this.includes, - this.excludes, - this.color, - this.elevation, - this.transparentOccluder}); - - final Iterable? includes; - final Iterable? excludes; - final Color? color; - final double? elevation; - final bool? transparentOccluder; - - static const Symbol symbol = #drawShadow; - String get methodName => _symbolName(symbol); - - @protected - void verifyArguments(List arguments) { - if (arguments.length != 4) { - throw 'It called $methodName with ${arguments.length} arguments; expected 4.'; - } - final Path pathArgument = arguments[0] as Path; - if (includes != null) { - for (final Offset offset in includes!) { - if (!pathArgument.contains(offset)) { - throw 'It called $methodName with a path that unexpectedly did not contain $offset.'; - } - } - } - if (excludes != null) { - for (final Offset offset in excludes!) { - if (pathArgument.contains(offset)) { - throw 'It called $methodName with a path that unexpectedly contained $offset.'; - } - } - } - final Color actualColor = arguments[1] as Color; - if (color != null && actualColor != color) { - throw 'It called $methodName with a color, $actualColor, which was not exactly the expected color ($color).'; - } - final double actualElevation = arguments[2] as double; - if (elevation != null && actualElevation != elevation) { - throw 'It called $methodName with an elevation, $actualElevation, which was not exactly the expected value ($elevation).'; - } - final bool actualTransparentOccluder = arguments[3] as bool; - if (transparentOccluder != null && - actualTransparentOccluder != transparentOccluder) { - throw 'It called $methodName with a transparentOccluder value, $actualTransparentOccluder, which was not exactly the expected value ($transparentOccluder).'; - } - } - - @override - void match(Iterator call) { - checkMethod(call, symbol); - verifyArguments(call.current.invocation.positionalArguments); - call.moveNext(); - } - - @protected - void debugFillDescription(List description) { - if (includes != null && excludes != null) { - description.add('that contains $includes and does not contain $excludes'); - } else if (includes != null) { - description.add('that contains $includes'); - } else if (excludes != null) { - description.add('that does not contain $excludes'); - } - if (color != null) { - description.add('$color'); - } - if (elevation != null) { - description.add('elevation: $elevation'); - } - if (transparentOccluder != null) { - description.add('transparentOccluder: $transparentOccluder'); - } - } - - @override - String toString() { - final List description = []; - debugFillDescription(description); - String result = methodName; - if (description.isNotEmpty) { - result += ' with ${description.join(", ")}'; - } - return result; - } -} - -class _DrawImagePaintPredicate extends _DrawCommandPaintPredicate { - _DrawImagePaintPredicate( - {this.image, - this.x, - this.y, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawImage, - 'an image', - 3, - 2, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); - - final ui.Image? image; - final double? x; - final double? y; - - @override - void verifyArguments(List arguments) { - super.verifyArguments(arguments); - final ui.Image imageArgument = arguments[0] as ui.Image; - if (image != null && !image!.isCloneOf(imageArgument)) { - throw 'It called $methodName with an image, $imageArgument, which was not exactly the expected image ($image).'; - } - final Offset pointArgument = arguments[0] as Offset; - if (x != null && y != null) { - final Offset point = Offset(x!, y!); - if (point != pointArgument) { - throw 'It called $methodName with an offset coordinate, $pointArgument, which was not exactly the expected coordinate ($point).'; - } - } else { - if (x != null && pointArgument.dx != x) { - throw 'It called $methodName with an offset coordinate, $pointArgument, whose x-coordinate not exactly the expected coordinate (${x!.toStringAsFixed(1)}).'; - } - if (y != null && pointArgument.dy != y) { - throw 'It called $methodName with an offset coordinate, $pointArgument, whose y-coordinate not exactly the expected coordinate (${y!.toStringAsFixed(1)}).'; - } - } - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (image != null) { - description.add('image $image'); - } - if (x != null && y != null) { - description.add('point ${Offset(x!, y!)}'); - } else { - if (x != null) { - description.add('x-coordinate ${x!.toStringAsFixed(1)}'); - } - if (y != null) { - description.add('y-coordinate ${y!.toStringAsFixed(1)}'); - } - } - } -} - -class _DrawImageRectPaintPredicate extends _DrawCommandPaintPredicate { - _DrawImageRectPaintPredicate( - {this.image, - this.source, - this.destination, - Color? color, - double? strokeWidth, - bool? hasMaskFilter, - PaintingStyle? style}) - : super( - #drawImageRect, - 'an image', - 4, - 3, - color: color, - strokeWidth: strokeWidth, - hasMaskFilter: hasMaskFilter, - style: style, - ); - - final ui.Image? image; - final Rect? source; - final Rect? destination; - - @override - void verifyArguments(List arguments) { - super.verifyArguments(arguments); - final ui.Image imageArgument = arguments[0] as ui.Image; - if (image != null && !image!.isCloneOf(imageArgument)) { - throw 'It called $methodName with an image, $imageArgument, which was not exactly the expected image ($image).'; - } - final Rect sourceArgument = arguments[1] as Rect; - if (source != null && sourceArgument != source) { - throw 'It called $methodName with a source rectangle, $sourceArgument, which was not exactly the expected rectangle ($source).'; - } - final Rect destinationArgument = arguments[2] as Rect; - if (destination != null && destinationArgument != destination) { - throw 'It called $methodName with a destination rectangle, $destinationArgument, which was not exactly the expected rectangle ($destination).'; - } - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - if (image != null) { - description.add('image $image'); - } - if (source != null) { - description.add('source $source'); - } - if (destination != null) { - description.add('destination $destination'); - } - } -} - -class _SomethingPaintPredicate extends _PaintPredicate { - _SomethingPaintPredicate(this.predicate); - - final PaintPatternPredicate predicate; - - @override - void match(Iterator call) { - RecordedInvocation currentCall; - bool testedAllCalls = false; - do { - if (testedAllCalls) { - throw 'It painted methods that the predicate passed to a "something" step, ' - 'in the paint pattern, none of which were considered correct.'; - } - currentCall = call.current; - if (!currentCall.invocation.isMethod) { - throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call'; - } - testedAllCalls = !call.moveNext(); - } while (!_runPredicate(currentCall.invocation.memberName, - currentCall.invocation.positionalArguments)); - } - - bool _runPredicate(Symbol methodName, List arguments) { - try { - return predicate(methodName, arguments); - } on String catch (s) { - throw 'It painted something that the predicate passed to a "something" step ' - 'in the paint pattern considered incorrect:\n $s\n '; - } - } - - @override - String toString() => 'a "something" step'; -} - -class _EverythingPaintPredicate extends _PaintPredicate { - _EverythingPaintPredicate(this.predicate); - - final PaintPatternPredicate predicate; - - @override - void match(Iterator call) { - do { - final RecordedInvocation currentCall = call.current; - if (!currentCall.invocation.isMethod) { - throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call'; - } - if (!_runPredicate(currentCall.invocation.memberName, - currentCall.invocation.positionalArguments)) { - throw 'It painted something that the predicate passed to an "everything" step ' - 'in the paint pattern considered incorrect.\n'; - } - } while (call.moveNext()); - } - - bool _runPredicate(Symbol methodName, List arguments) { - try { - return predicate(methodName, arguments); - } on String catch (s) { - throw 'It painted something that the predicate passed to an "everything" step ' - 'in the paint pattern considered incorrect:\n $s\n '; - } - } - - @override - String toString() => 'an "everything" step'; -} - -class _FunctionPaintPredicate extends _PaintPredicate { - _FunctionPaintPredicate(this.symbol, this.arguments); - - final Symbol symbol; - - final List arguments; - - @override - void match(Iterator call) { - checkMethod(call, symbol); - if (call.current.invocation.positionalArguments.length != - arguments.length) { - throw 'It called ${_symbolName(symbol)} with ${call.current.invocation.positionalArguments.length} arguments; expected ${arguments.length}.'; - } - for (int index = 0; index < arguments.length; index += 1) { - final dynamic actualArgument = - call.current.invocation.positionalArguments[index]; - final dynamic desiredArgument = arguments[index]; - - if (desiredArgument is Matcher) { - expect(actualArgument, desiredArgument); - } else if (desiredArgument != null && desiredArgument != actualArgument) { - throw 'It called ${_symbolName(symbol)} with argument $index having value ${_valueName(actualArgument)} when ${_valueName(desiredArgument)} was expected.'; - } - } - call.moveNext(); - } - - @override - String toString() { - final List adjectives = [ - for (int index = 0; index < arguments.length; index += 1) - arguments[index] != null ? _valueName(arguments[index]) : '...', - ]; - return '${_symbolName(symbol)}(${adjectives.join(", ")})'; - } -} - -class _SaveRestorePairPaintPredicate extends _PaintPredicate { - @override - void match(Iterator call) { - checkMethod(call, #save); - int depth = 1; - while (depth > 0) { - if (!call.moveNext()) { - throw 'It did not have a matching restore() for the save() that was found where $this was expected.'; - } - if (call.current.invocation.isMethod) { - if (call.current.invocation.memberName == #save) { - depth += 1; - } else if (call.current.invocation.memberName == #restore) { - depth -= 1; - } - } - } - call.moveNext(); - } - - @override - String toString() => 'a matching save/restore pair'; -} - -String _valueName(Object? value) { - if (value is double) { - return value.toStringAsFixed(1); - } - return value.toString(); -} - -// Workaround for https://github.com/dart-lang/sdk/issues/28372 -String _symbolName(Symbol symbol) { - // WARNING: Assumes a fixed format for Symbol.toString which is *not* - // guaranteed anywhere. - final String s = '$symbol'; - return s.substring(8, s.length - 2); -} diff --git a/boxy/test/recording_canvas.dart b/boxy/test/recording_canvas.dart deleted file mode 100644 index 62f73b8..0000000 --- a/boxy/test/recording_canvas.dart +++ /dev/null @@ -1,256 +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/rendering.dart'; - -/// An [Invocation] and the [stack] trace that led to it. -/// -/// Used by [TestRecordingCanvas] to trace canvas calls. -class RecordedInvocation { - /// Create a record for an invocation list. - const RecordedInvocation(this.invocation, {required this.stack}); - - /// The method that was called and its arguments. - /// - /// The arguments preserve identity, but not value. Thus, if two invocations - /// were made with the same [Paint] object, but with that object configured - /// differently each time, then they will both have the same object as their - /// argument, and inspecting that object will return the object's current - /// values (mostly likely those passed to the second call). - final Invocation invocation; - - /// The stack trace at the time of the method call. - final StackTrace stack; - - @override - String toString() => _describeInvocation(invocation); - - /// Converts [stack] to a string using the [FlutterError.defaultStackFilter] logic. - String stackToString({String indent = ''}) { - return indent + - FlutterError.defaultStackFilter( - stack.toString().trimRight().split('\n')) - .join('\n$indent'); - } -} - -/// A [Canvas] for tests that records its method calls. -/// -/// This class can be used in conjunction with [TestRecordingPaintingContext] -/// to record the [Canvas] method calls made by a renderer. For example: -/// -/// ```dart -/// RenderBox box = tester.renderObject(find.text('ABC')); -/// TestRecordingCanvas canvas = TestRecordingCanvas(); -/// TestRecordingPaintingContext context = TestRecordingPaintingContext(canvas); -/// box.paint(context, Offset.zero); -/// // Now test the expected canvas.invocations. -/// ``` -/// -/// In some cases it may be useful to define a subclass that overrides the -/// [Canvas] methods the test is checking and squirrels away the parameters -/// that the test requires. -/// -/// For simple tests, consider using the [paints] matcher, which overlays a -/// pattern matching API over [TestRecordingCanvas]. -class TestRecordingCanvas implements Canvas { - /// All of the method calls on this canvas. - final List invocations = []; - - int _saveCount = 0; - - @override - int getSaveCount() => _saveCount; - - @override - void save() { - _saveCount += 1; - invocations - .add(RecordedInvocation(_MethodCall(#save), stack: StackTrace.current)); - } - - @override - void saveLayer(Rect? bounds, Paint paint) { - _saveCount += 1; - invocations.add(RecordedInvocation( - _MethodCall(#saveLayer, [bounds, paint]), - stack: StackTrace.current)); - } - - @override - void restore() { - _saveCount -= 1; - assert(_saveCount >= 0); - invocations.add( - RecordedInvocation(_MethodCall(#restore), stack: StackTrace.current)); - } - - @override - void noSuchMethod(Invocation invocation) { - invocations.add(RecordedInvocation(invocation, stack: StackTrace.current)); - } -} - -/// A [PaintingContext] for tests that use [TestRecordingCanvas]. -class TestRecordingPaintingContext extends ClipContext - implements PaintingContext { - /// Creates a [PaintingContext] for tests that use [TestRecordingCanvas]. - TestRecordingPaintingContext(this.canvas); - - @override - final Canvas canvas; - - @override - void paintChild(RenderObject child, Offset offset) { - child.paint(this, offset); - } - - @override - ClipRectLayer? pushClipRect( - bool needsCompositing, - Offset offset, - Rect clipRect, - PaintingContextCallback painter, { - Clip clipBehavior = Clip.hardEdge, - ClipRectLayer? oldLayer, - }) { - clipRectAndPaint(clipRect.shift(offset), clipBehavior, - clipRect.shift(offset), () => painter(this, offset)); - return null; - } - - @override - ClipRRectLayer? pushClipRRect( - bool needsCompositing, - Offset offset, - Rect bounds, - RRect clipRRect, - PaintingContextCallback painter, { - Clip clipBehavior = Clip.antiAlias, - ClipRRectLayer? oldLayer, - }) { - clipRRectAndPaint(clipRRect.shift(offset), clipBehavior, - bounds.shift(offset), () => painter(this, offset)); - return null; - } - - @override - ClipPathLayer? pushClipPath( - bool needsCompositing, - Offset offset, - Rect bounds, - Path clipPath, - PaintingContextCallback painter, { - Clip clipBehavior = Clip.antiAlias, - ClipPathLayer? oldLayer, - }) { - clipPathAndPaint(clipPath.shift(offset), clipBehavior, bounds.shift(offset), - () => painter(this, offset)); - return null; - } - - @override - TransformLayer? pushTransform( - bool needsCompositing, - Offset offset, - Matrix4 transform, - PaintingContextCallback painter, { - TransformLayer? oldLayer, - }) { - canvas.save(); - canvas.transform(transform.storage); - painter(this, offset); - canvas.restore(); - return null; - } - - @override - OpacityLayer pushOpacity( - Offset offset, - int alpha, - PaintingContextCallback painter, { - OpacityLayer? oldLayer, - }) { - canvas.saveLayer(null, Paint()); // TODO(ianh): Expose the alpha somewhere. - painter(this, offset); - canvas.restore(); - return OpacityLayer(); - } - - @override - void pushLayer( - Layer childLayer, - PaintingContextCallback painter, - Offset offset, { - Rect? childPaintBounds, - }) { - painter(this, offset); - } - - @override - void noSuchMethod(Invocation invocation) {} -} - -class _MethodCall implements Invocation { - _MethodCall(this._name, - [this._arguments = const [], - // ignore: unused_element - this._typeArguments = const []]); - final Symbol _name; - final List _arguments; - final List _typeArguments; - @override - bool get isAccessor => false; - @override - bool get isGetter => false; - @override - bool get isMethod => true; - @override - bool get isSetter => false; - @override - Symbol get memberName => _name; - @override - Map get namedArguments => {}; - @override - List get positionalArguments => _arguments; - @override - List get typeArguments => _typeArguments; -} - -String _valueName(Object? value) { - if (value is double) { - return value.toStringAsFixed(1); - } - return value.toString(); -} - -// Workaround for https://github.com/dart-lang/sdk/issues/28372 -String _symbolName(Symbol symbol) { - // WARNING: Assumes a fixed format for Symbol.toString which is *not* - // guaranteed anywhere. - final String s = '$symbol'; - return s.substring(8, s.length - 2); -} - -// Workaround for https://github.com/dart-lang/sdk/issues/28373 -String _describeInvocation(Invocation call) { - final StringBuffer buffer = StringBuffer(); - buffer.write(_symbolName(call.memberName)); - if (call.isSetter) { - buffer.write(call.positionalArguments[0].toString()); - } else if (call.isMethod) { - buffer.write('('); - buffer.writeAll(call.positionalArguments.map(_valueName), ', '); - String separator = call.positionalArguments.isEmpty ? '' : ', '; - call.namedArguments.forEach((Symbol name, Object? value) { - buffer.write(separator); - buffer.write(_symbolName(name)); - buffer.write(': '); - buffer.write(_valueName(value)); - separator = ', '; - }); - buffer.write(')'); - } - return buffer.toString(); -} diff --git a/boxy/test/sliver_container_test.dart b/boxy/test/sliver_container_test.dart index 71b64de..4c35045 100644 --- a/boxy/test/sliver_container_test.dart +++ b/boxy/test/sliver_container_test.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'common.dart'; -import 'mock_canvas.dart'; class NotifyClipper extends CustomClipper { final ValueNotifier notifier;