diff --git a/playbook_snapshot/lib/src/snapshot_support.dart b/playbook_snapshot/lib/src/snapshot_support.dart index db8c618..a21f925 100644 --- a/playbook_snapshot/lib/src/snapshot_support.dart +++ b/playbook_snapshot/lib/src/snapshot_support.dart @@ -7,6 +7,9 @@ import 'package:playbook/playbook.dart'; import 'snapshot_device.dart'; class SnapshotSupport { + static const _maxTryResizeCount = 10; + static const _maxSnapshotSize = 50000; + static Future startDevice( Widget target, WidgetTester tester, @@ -38,25 +41,43 @@ class SnapshotSupport { // We use scrollController.maxScrollExtent to calculate the snapshot size. // However, maxScrollExtent may report incorrectly. // To solve this, we repeatedly calculate size and update size until we can get a stable value. - var lastExtendedSize = device.size; + var lastExtendedSize = Size( + scenario.layout.compressedResizingTarget.needResizingWidth + ? device.size.width + : absoluteSize.width, + scenario.layout.compressedResizingTarget.needResizingHeight + ? device.size.height + : absoluteSize.height, + ); + var resize = 0; while (true) { - final scrollViews = find - .byWidgetPredicate((widget) => widget is ScrollView) + final scrollables = find + .byWidgetPredicate((widget) => widget is Scrollable) .evaluate() - .map((e) => e.widget as ScrollView); - if (scrollViews.isEmpty) break; + .map((e) => e.widget as Scrollable); + if (scrollables.isEmpty) break; var extendedSize = device.size; - for (final scrollView in scrollViews) { + for (final scrollable in scrollables) { extendedSize = _extendScrollableSnapshotSize( - scrollView: scrollView, - extendedSize: extendedSize, + scrollable: scrollable, + currentExtendedSize: extendedSize, originSize: lastExtendedSize, + resizingTarget: scenario.layout.compressedResizingTarget, ); } if (extendedSize <= lastExtendedSize) break; lastExtendedSize = extendedSize; await _setSnapshotSize(tester, lastExtendedSize); + resize++; + if (resize >= _maxTryResizeCount) { + throw StateError( + 'Try resizing too many times. Please try to set your scenario to have a fixed size.'); + } + if (extendedSize.width >= _maxSnapshotSize || extendedSize.height >= _maxSnapshotSize) { + throw StateError( + 'Try resizing too large size ${extendedSize}. Please try to set your scenario to have a fixed size.'); + } } snapshotSize = lastExtendedSize; } else { @@ -86,31 +107,47 @@ class SnapshotSupport { } static Size _extendScrollableSnapshotSize({ - required ScrollView scrollView, - required Size extendedSize, + required Scrollable scrollable, + required Size currentExtendedSize, required Size originSize, + required _CompressedResizingTarget resizingTarget, }) { - final controller = scrollView.controller; - if (controller == null) { + final controller = scrollable.controller; + ScrollPosition? position; + try { + position = controller?.position; + } catch (_) {} + if (position == null) { return Size( - max(extendedSize.width, originSize.width), - max(extendedSize.height, originSize.height), + resizingTarget.needResizingWidth + ? max(currentExtendedSize.width, originSize.width) + : originSize.width, + resizingTarget.needResizingHeight + ? max(currentExtendedSize.height, originSize.height) + : originSize.height, ); } - final scrollAxis = controller.position.axis; - final maxScrollExtent = controller.position.maxScrollExtent; + final scrollAxis = position.axis; + final maxScrollExtent = position.maxScrollExtent; + final Size newExtendedSize; switch (scrollAxis) { case Axis.horizontal: - final height = max(originSize.height, extendedSize.height); - final width = max(maxScrollExtent + originSize.width, extendedSize.width); - return Size(width, height); + final height = max(originSize.height, currentExtendedSize.height); + final width = max(maxScrollExtent + originSize.width, currentExtendedSize.width); + newExtendedSize = Size(width, height); + break; case Axis.vertical: - final height = max(maxScrollExtent + originSize.height, extendedSize.height); - final width = max(originSize.width, extendedSize.width); - return Size(width, height); + final height = max(maxScrollExtent + originSize.height, currentExtendedSize.height); + final width = max(originSize.width, currentExtendedSize.width); + newExtendedSize = Size(width, height); + break; } + return Size( + resizingTarget.needResizingWidth ? newExtendedSize.width : originSize.width, + resizingTarget.needResizingHeight ? newExtendedSize.height : originSize.height, + ); } } @@ -123,6 +160,18 @@ extension on ScenarioLayout { return v is ScenarioLayoutCompressed || h is ScenarioLayoutCompressed; } + _CompressedResizingTarget get compressedResizingTarget { + if (v is ScenarioLayoutCompressed && h is ScenarioLayoutCompressed) { + return _CompressedResizingTarget.both; + } else if (v is ScenarioLayoutCompressed) { + return _CompressedResizingTarget.vertical; + } else if (h is ScenarioLayoutCompressed) { + return _CompressedResizingTarget.horizontal; + } else { + throw StateError('No need compressed resizing.'); + } + } + double absoluteWidth(SnapshotDevice device) { switch (h.runtimeType) { case ScenarioLayoutFixed: @@ -147,3 +196,17 @@ extension on ScenarioLayout { return device.size.height; } } + +enum _CompressedResizingTarget { + horizontal, + vertical, + both, +} + +extension on _CompressedResizingTarget { + bool get needResizingWidth => + this == _CompressedResizingTarget.both || this == _CompressedResizingTarget.horizontal; + + bool get needResizingHeight => + this == _CompressedResizingTarget.both || this == _CompressedResizingTarget.vertical; +}