From f15851203d0903b915210a1b5abce5ee195b1837 Mon Sep 17 00:00:00 2001 From: Baki Celebi Date: Mon, 21 Aug 2023 16:03:57 +0200 Subject: [PATCH 1/3] PAINTROID-654 : Remove tests from exluded folders and fix issues --- .github/workflows/main.yml | 6 +++-- analysis_options.yaml | 1 - pubspec.lock | 26 +++++++++++++++++- pubspec.yaml | 1 + test/unit/io/service/file_service_test.dart | 27 ++++++++++--------- .../service/photo_library_service_test.dart | 14 +++++----- .../io/usecase/save_as_raster_image_test.dart | 12 ++++----- test/unit/tool/brush_tool_test.dart | 1 - .../usecase/render_image_for_export_test.dart | 6 ++--- test/widget/ui/landing_page_test.dart | 6 ++--- 10 files changed, 62 insertions(+), 38 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95d86ddb..d56cf780 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,11 +11,13 @@ jobs: uses: arduino/setup-protoc@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: subosito/flutter-action@v2.5.0 + - uses: subosito/flutter-action@v2.10.0 with: flutter-version: '3.10.5' channel: 'stable' - cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + cache-path: '${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:' + architecture: x64 # optional, x64 or arm64 - name: Setup run: | flutter pub get diff --git a/analysis_options.yaml b/analysis_options.yaml index 667bff65..f26de79a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -22,4 +22,3 @@ analyzer: exclude: - lib/**.pb*.dart - lib/data/*.g.dart - - test/** diff --git a/pubspec.lock b/pubspec.lock index 780447f3..9811a1c3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -241,6 +241,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" + url: "https://pub.dev" + source: hosted + version: "9.0.3" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + url: "https://pub.dev" + source: hosted + version: "7.0.0" equatable: dependency: "direct main" description: @@ -837,7 +853,7 @@ packages: source: hosted version: "1.2.3" riverpod: - dependency: transitive + dependency: "direct dev" description: name: riverpod sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef" @@ -1257,6 +1273,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f8bc214a..a5e01859 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,7 @@ dev_dependencies: riverpod_generator: ^2.2.3 riverpod_lint: ^1.3.2 freezed: ^2.4.1 + riverpod: ^2.3.7 flutter: generate: true diff --git a/test/unit/io/service/file_service_test.dart b/test/unit/io/service/file_service_test.dart index d8ebe02e..f62e0633 100644 --- a/test/unit/io/service/file_service_test.dart +++ b/test/unit/io/service/file_service_test.dart @@ -15,8 +15,10 @@ void main() async { const channel = MethodChannel( 'plugins.flutter.io/path_provider', ); - channel - .setMockMethodCallHandler((MethodCall methodCall) async => testDirectory); + final defaultBinaryMessenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + defaultBinaryMessenger.setMockMethodCallHandler( + channel, (MethodCall methodCall) async => testDirectory); setUp(() async { sut = FileService(); @@ -51,17 +53,17 @@ void main() async { final file = result.unwrapOrElse((failure) => fail(failure.message)); expect(file, isA()); - final resultDeleted = await sut.deleteFileInApplicationDirectory( - 'test1.png' - ); - final deleted = resultDeleted.unwrapOrElse((failure) => fail(failure.message)); + final resultDeleted = + await sut.deleteFileInApplicationDirectory('test1.png'); + final deleted = + resultDeleted.unwrapOrElse((failure) => fail(failure.message)); expect(deleted, isA()); }); - test('Should save file to Application directory and check should return true', () async { - final fileDoesNotExist = await sut.checkIfFileExistsInApplicationDirectory( - 'test1.png' - ); + test('Should save file to Application directory and check should return true', + () async { + final fileDoesNotExist = + await sut.checkIfFileExistsInApplicationDirectory('test1.png'); expect(fileDoesNotExist, false); @@ -72,9 +74,8 @@ void main() async { final file = result.unwrapOrElse((failure) => fail(failure.message)); expect(file, isA()); - final fileExists = await sut.checkIfFileExistsInApplicationDirectory( - 'test1.png' - ); + final fileExists = + await sut.checkIfFileExistsInApplicationDirectory('test1.png'); expect(fileExists, true); }); diff --git a/test/unit/io/service/photo_library_service_test.dart b/test/unit/io/service/photo_library_service_test.dart index a4ae2e7e..29d06912 100644 --- a/test/unit/io/service/photo_library_service_test.dart +++ b/test/unit/io/service/photo_library_service_test.dart @@ -50,7 +50,7 @@ void main() { 'data': testImageData, }; final result = await sut.save(testFilename, testImageData); - expect(result, Ok(unit)); + expect(result, const Ok(unit)); verify(mockMethodChannel.invokeMethod('saveToPhotos', expectedArgs)); verifyNoMoreInteractions(mockMethodChannel); verifyZeroInteractions(mockImagePicker); @@ -62,7 +62,7 @@ void main() { () async { when(mockMethodChannel.invokeMethod(any, any)).thenThrow(testException); final result = await sut.save(testFilename, testImageData); - expect(result, Err(SaveImageFailure.unidentified)); + expect(result, const Err(SaveImageFailure.unidentified)); verify(mockMethodChannel.invokeMethod(any, any)); verifyNoMoreInteractions(mockMethodChannel); verifyZeroInteractions(mockImagePicker); @@ -75,7 +75,7 @@ void main() { when(mockMethodChannel.invokeMethod(any, any)) .thenThrow(testPlatformException); final result = await sut.save(testFilename, testImageData); - expect(result, Err(SaveImageFailure.unidentified)); + expect(result, const Err(SaveImageFailure.unidentified)); verify(mockMethodChannel.invokeMethod(any, any)); verifyNoMoreInteractions(mockMethodChannel); verifyZeroInteractions(mockImagePicker); @@ -108,7 +108,7 @@ void main() { when(mockImagePicker.pickImage(source: anyNamed('source'))) .thenAnswer((_) async => null); final result = await sut.pick(); - expect(result, Err(LoadImageFailure.userCancelled)); + expect(result, const Err(LoadImageFailure.userCancelled)); verify(mockImagePicker.pickImage(source: anyNamed('source'))); verifyNoMoreInteractions(mockImagePicker); verifyZeroInteractions(mockMethodChannel); @@ -121,7 +121,7 @@ void main() { when(mockImagePicker.pickImage(source: anyNamed('source'))) .thenThrow(testException); final result = await sut.pick(); - expect(result, Err(LoadImageFailure.unidentified)); + expect(result, const Err(LoadImageFailure.unidentified)); verify(mockImagePicker.pickImage(source: anyNamed('source'))); verifyNoMoreInteractions(mockImagePicker); verifyZeroInteractions(mockMethodChannel); @@ -133,7 +133,7 @@ void main() { .thenAnswer((_) async => mockImageXFile); when(mockImageXFile.readAsBytes()).thenThrow(testException); final result = await sut.pick(); - expect(result, Err(LoadImageFailure.unidentified)); + expect(result, const Err(LoadImageFailure.unidentified)); verify(mockImagePicker.pickImage(source: anyNamed('source'))); verify(mockImageXFile.readAsBytes()); verifyNoMoreInteractions(mockImageXFile); @@ -148,7 +148,7 @@ void main() { when(mockImagePicker.pickImage(source: anyNamed('source'))) .thenThrow(testPlatformException); final result = await sut.pick(); - expect(result, Err(LoadImageFailure.unidentified)); + expect(result, const Err(LoadImageFailure.unidentified)); verify(mockImagePicker.pickImage(source: anyNamed('source'))); verifyNoMoreInteractions(mockImagePicker); verifyZeroInteractions(mockMethodChannel); diff --git a/test/unit/io/usecase/save_as_raster_image_test.dart b/test/unit/io/usecase/save_as_raster_image_test.dart index 528655e8..78a68dda 100644 --- a/test/unit/io/usecase/save_as_raster_image_test.dart +++ b/test/unit/io/usecase/save_as_raster_image_test.dart @@ -65,10 +65,10 @@ void main() { when(mockImageService.exportAsPng(any)) .thenAnswer((_) async => Ok(fakeBytes)); when(mockPhotoLibraryService.save(any, any)) - .thenAnswer((_) async => Ok(unit)); + .thenAnswer((_) async => const Ok(unit)); final testMetaData = PngMetaData(testName); final result = await sut(testMetaData, fakeImage); - expect(result, Ok(unit)); + expect(result, const Ok(unit)); verify(mockPermissionService.requestAccessForSavingToPhotos()); verify(mockImageService.exportAsPng(fakeImage)); verify(mockPhotoLibraryService.save(expectedFilename, fakeBytes)); @@ -84,10 +84,10 @@ void main() { when(mockImageService.exportAsJpg(any, any)) .thenAnswer((_) async => Ok(fakeBytes)); when(mockPhotoLibraryService.save(any, any)) - .thenAnswer((_) async => Ok(unit)); + .thenAnswer((_) async => const Ok(unit)); final testMetaData = JpgMetaData(testName, testQuality); final result = await sut(testMetaData, fakeImage); - expect(result, Ok(unit)); + expect(result, const Ok(unit)); verify(mockPermissionService.requestAccessForSavingToPhotos()); verify(mockImageService.exportAsJpg(fakeImage, testQuality)); verify(mockPhotoLibraryService.save(expectedFilename, fakeBytes)); @@ -183,7 +183,7 @@ void main() { .thenAnswer((_) async => false); final testMetaData = JpgMetaData(testName, testQuality); final result = await sut(testMetaData, fakeImage); - expect(result, Err(SaveImageFailure.permissionDenied)); + expect(result, const Err(SaveImageFailure.permissionDenied)); verify(mockPermissionService.requestAccessForSavingToPhotos()); verifyNoMoreInteractions(mockPermissionService); verifyZeroInteractions(mockImageService); @@ -195,7 +195,7 @@ void main() { .thenAnswer((_) async => false); final testMetaData = PngMetaData(testName); final result = await sut(testMetaData, fakeImage); - expect(result, Err(SaveImageFailure.permissionDenied)); + expect(result, const Err(SaveImageFailure.permissionDenied)); verify(mockPermissionService.requestAccessForSavingToPhotos()); verifyNoMoreInteractions(mockPermissionService); verifyZeroInteractions(mockImageService); diff --git a/test/unit/tool/brush_tool_test.dart b/test/unit/tool/brush_tool_test.dart index 2a0ebc42..8a6b9141 100644 --- a/test/unit/tool/brush_tool_test.dart +++ b/test/unit/tool/brush_tool_test.dart @@ -6,7 +6,6 @@ import 'package:mockito/mockito.dart'; import 'package:paintroid/command/command.dart'; import 'package:paintroid/core/graphic_factory.dart'; import 'package:paintroid/core/path_with_action_history.dart'; -import 'package:paintroid/tool/src/tool_types.dart'; import 'package:paintroid/tool/tool.dart'; import 'brush_tool_test.mocks.dart'; diff --git a/test/unit/workspace/usecase/render_image_for_export_test.dart b/test/unit/workspace/usecase/render_image_for_export_test.dart index e30fdce3..bf6c3f74 100644 --- a/test/unit/workspace/usecase/render_image_for_export_test.dart +++ b/test/unit/workspace/usecase/render_image_for_export_test.dart @@ -89,8 +89,8 @@ class FakeGraphicCommand extends Fake implements GraphicCommand {} @GenerateMocks( [], customMocks: [ - MockSpec(returnNullOnMissingStub: true), - MockSpec(returnNullOnMissingStub: true), + MockSpec(), + MockSpec(), ], ) void main() { @@ -145,7 +145,6 @@ void main() { final testCanvasRect = Rect.fromLTRB(0, 0, testCanvasSize.width, testCanvasSize.height); late Paint testPaint; - late Offset testOffset; late MockCanvas mockBackgroundCanvas; late MockCanvas mockCommandsCanvas; late MockCanvas mockCombinedCanvas; @@ -154,7 +153,6 @@ void main() { setUp(() { testPaint = Paint(); - testOffset = const Offset(0.0, 0.0); mockBackgroundCanvas = MockCanvas(); mockCommandsCanvas = MockCanvas(); mockCombinedCanvas = MockCanvas(); diff --git a/test/widget/ui/landing_page_test.dart b/test/widget/ui/landing_page_test.dart index a66ad3b3..44facaae 100644 --- a/test/widget/ui/landing_page_test.dart +++ b/test/widget/ui/landing_page_test.dart @@ -39,7 +39,7 @@ void main() { late ui.Image dummyImage; final DateFormat formatter = DateFormat('dd-MM-yyyy HH:mm:ss'); - Project _createProject(String name) => Project( + Project createProject(String name) => Project( name: name, path: filePath, imagePreviewPath: filePath, @@ -62,7 +62,7 @@ void main() { showOnboardingPage: false, ), ); - projects = List.generate(5, (index) => _createProject('project$index')); + projects = List.generate(5, (index) => createProject('project$index')); dummyImage = await createTestImage(width: 1080, height: 1920); }); @@ -390,7 +390,7 @@ void main() { when(database.projectDAO).thenReturn(dao); when(dao.getProjects()) - .thenAnswer((_) => Future.value([_createProject(projectName)])); + .thenAnswer((_) => Future.value([createProject(projectName)])); when(fileService.checkIfFileExistsInApplicationDirectory(projectName)) .thenAnswer((_) => Future.value(true)); when(imageService.getProjectPreview(filePath)) From 55bd604912fe2c617deda45313fd15bdd839e3f5 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Thu, 16 Nov 2023 21:21:03 +0100 Subject: [PATCH 2/3] PAINTROID-591 Add Makefile --- Makefile | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..ea6c04f4 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +.PHONY: pubget build watch clean test analyze test-unit test-widget test-all all + +clean: + flutter clean + +pubget: + flutter pub get + +build: + dart run build_runner build --delete-conflicting-outputs + +run: + flutter run + +all: clean pubget build run + +watch: + dart run build_runner watch --delete-conflicting-outputs + +analyze: + flutter analyze + +test-unit: + flutter test test/unit + +test-widget: + flutter test test/widget + +test-all: + flutter test From d5efbbc00763f0c07a43e6d2ec91bdf6e37546e2 Mon Sep 17 00:00:00 2001 From: Baki Celebi Date: Sat, 9 Sep 2023 12:25:54 +0200 Subject: [PATCH 3/3] PAINTROID-658 : Add hand tool --- lib/tool/src/hand_tool/hand_tool.dart | 47 ++++++++++++ .../src/hand_tool/hand_tool_provider.dart | 18 +++++ .../src/toolbox/toolbox_state_provider.dart | 7 ++ lib/workspace/src/ui/drawing_canvas.dart | 14 ++-- test/unit/tool/brush_tool_test.dart | 1 - test/unit/tool/hand_tool_test.dart | 72 +++++++++++++++++++ test/widget/tool/eraser_tool_test.dart | 4 +- test/widget/tool/hand_tool_test.dart | 68 ++++++++++++++++++ .../interactions/canvas_interactions.dart | 2 +- .../interactive_viewer_interactions.dart | 35 +++++++++ 10 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 lib/tool/src/hand_tool/hand_tool.dart create mode 100644 lib/tool/src/hand_tool/hand_tool_provider.dart create mode 100644 test/unit/tool/hand_tool_test.dart create mode 100644 test/widget/tool/hand_tool_test.dart create mode 100644 test/widget/utils/interactions/interactive_viewer_interactions.dart diff --git a/lib/tool/src/hand_tool/hand_tool.dart b/lib/tool/src/hand_tool/hand_tool.dart new file mode 100644 index 00000000..cd60dad9 --- /dev/null +++ b/lib/tool/src/hand_tool/hand_tool.dart @@ -0,0 +1,47 @@ +import 'dart:ui'; + +import 'package:equatable/equatable.dart'; +import 'package:paintroid/command/src/command_factory.dart'; +import 'package:paintroid/command/src/command_manager.dart'; +import 'package:paintroid/core/graphic_factory.dart'; +import 'package:paintroid/tool/src/tool.dart'; +import 'package:paintroid/tool/src/tool_types.dart'; + +class HandTool extends Tool with EquatableMixin { + HandTool({ + required super.paint, + required super.commandFactory, + required super.commandManager, + required super.type, + }); + + @override + void onDown(Offset point) {} + + @override + void onDrag(Offset point) {} + + @override + void onUp(Offset? point) {} + + @override + void onCancel() {} + + @override + List get props => [commandManager, commandFactory]; + + HandTool copyWith({ + Paint? paint, + CommandFactory? commandFactory, + CommandManager? commandManager, + GraphicFactory? graphicFactory, + ToolType? type, + }) { + return HandTool( + paint: paint ?? this.paint, + commandFactory: commandFactory ?? this.commandFactory, + commandManager: commandManager ?? this.commandManager, + type: type ?? this.type, + ); + } +} diff --git a/lib/tool/src/hand_tool/hand_tool_provider.dart b/lib/tool/src/hand_tool/hand_tool_provider.dart new file mode 100644 index 00000000..fc3e5ed2 --- /dev/null +++ b/lib/tool/src/hand_tool/hand_tool_provider.dart @@ -0,0 +1,18 @@ +import 'package:paintroid/command/src/command_factory_provider.dart'; +import 'package:paintroid/command/src/command_manager_provider.dart'; +import 'package:paintroid/tool/src/brush_tool/brush_tool_state_provider.dart'; +import 'package:paintroid/tool/src/hand_tool/hand_tool.dart'; +import 'package:paintroid/tool/src/tool_types.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'hand_tool_provider.g.dart'; + +@riverpod +HandTool handTool(HandToolRef ref) { + return HandTool( + paint: ref.watch(brushToolStateProvider.select((state) => state.paint)), + type: ToolType.HAND, + commandManager: ref.watch(commandManagerProvider), + commandFactory: ref.watch(commandFactoryProvider), + ); +} diff --git a/lib/tool/src/toolbox/toolbox_state_provider.dart b/lib/tool/src/toolbox/toolbox_state_provider.dart index 719354e6..5c08008b 100644 --- a/lib/tool/src/toolbox/toolbox_state_provider.dart +++ b/lib/tool/src/toolbox/toolbox_state_provider.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:paintroid/tool/src/hand_tool/hand_tool_provider.dart'; import 'package:paintroid/tool/tool.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:toast/toast.dart'; @@ -38,6 +39,12 @@ class ToolBoxState extends _$ToolBoxState { currentToolType: ToolType.BRUSH, ); + break; + case ToolType.HAND: + state = state.copyWith( + currentTool: ref.read(handToolProvider), + currentToolType: ToolType.HAND, + ); break; case ToolType.ERASER: ref diff --git a/lib/workspace/src/ui/drawing_canvas.dart b/lib/workspace/src/ui/drawing_canvas.dart index 44418bd1..def06be6 100644 --- a/lib/workspace/src/ui/drawing_canvas.dart +++ b/lib/workspace/src/ui/drawing_canvas.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:paintroid/service/device_service.dart'; +import 'package:paintroid/tool/src/tool_types.dart'; import 'package:paintroid/tool/src/toolbox/toolbox_state_provider.dart'; import 'package:paintroid/workspace/src/state/canvas/canvas_state_provider.dart'; import 'package:paintroid/workspace/src/state/canvas_dirty_state.dart'; @@ -114,23 +115,18 @@ class _DrawingCanvasState extends ConsumerState { _resetCanvasScale(fitToScreen: isFullscreen); }, ); - final canvasSize = ref.watch( - canvasStateProvider.select((state) => state.size), - ); - final panningMargin = (canvasSize - const Offset(5, 5)) as Size; return Listener( onPointerDown: _onPointerDown, onPointerUp: _onPointerUp, child: InteractiveViewer( clipBehavior: Clip.none, transformationController: _transformationController, - boundaryMargin: EdgeInsets.symmetric( - horizontal: panningMargin.width, - vertical: panningMargin.height, - ), minScale: 0.2, maxScale: 100, - panEnabled: false, + boundaryMargin: const EdgeInsets.all(double.infinity), + interactionEndFrictionCoefficient: double.minPositive, + panEnabled: + ref.watch(toolBoxStateProvider).currentTool.type == ToolType.HAND, onInteractionStart: _onInteractionStart, onInteractionUpdate: _onInteractionUpdate, onInteractionEnd: _onInteractionEnd, diff --git a/test/unit/tool/brush_tool_test.dart b/test/unit/tool/brush_tool_test.dart index 2a0ebc42..8a6b9141 100644 --- a/test/unit/tool/brush_tool_test.dart +++ b/test/unit/tool/brush_tool_test.dart @@ -6,7 +6,6 @@ import 'package:mockito/mockito.dart'; import 'package:paintroid/command/command.dart'; import 'package:paintroid/core/graphic_factory.dart'; import 'package:paintroid/core/path_with_action_history.dart'; -import 'package:paintroid/tool/src/tool_types.dart'; import 'package:paintroid/tool/tool.dart'; import 'brush_tool_test.mocks.dart'; diff --git a/test/unit/tool/hand_tool_test.dart b/test/unit/tool/hand_tool_test.dart new file mode 100644 index 00000000..c6caa5b3 --- /dev/null +++ b/test/unit/tool/hand_tool_test.dart @@ -0,0 +1,72 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:paintroid/command/src/command_factory.dart'; +import 'package:paintroid/command/src/command_manager.dart'; +import 'package:paintroid/tool/src/hand_tool/hand_tool.dart'; +import 'package:paintroid/tool/src/tool_types.dart'; + +import 'hand_tool_test.mocks.dart'; + +@GenerateMocks([ + Paint, + CommandManager, + CommandFactory, +]) +void main() { + late MockPaint mockPaint; + late MockCommandFactory mockCommandFactory; + late MockCommandManager mockCommandManager; + + late HandTool sut; + const Offset offset = Offset(10, 10); + + setUp(() { + mockPaint = MockPaint(); + mockCommandFactory = MockCommandFactory(); + mockCommandManager = MockCommandManager(); + + sut = HandTool( + paint: mockPaint, + commandFactory: mockCommandFactory, + commandManager: mockCommandManager, + type: ToolType.HAND, + ); + }); + + group('HandTool Tests', () { + test('onDown should not interact with any dependencies', () { + sut.onDown(offset); + + verifyNoMoreInteractions(mockPaint); + verifyNoMoreInteractions(mockCommandFactory); + verifyNoMoreInteractions(mockCommandManager); + }); + + test('onDrag should not interact with any dependencies', () { + sut.onDrag(offset); + + verifyNoMoreInteractions(mockPaint); + verifyNoMoreInteractions(mockCommandFactory); + verifyNoMoreInteractions(mockCommandManager); + }); + + test('onUp should not interact with any dependencies', () { + sut.onUp(offset); + + verifyNoMoreInteractions(mockPaint); + verifyNoMoreInteractions(mockCommandFactory); + verifyNoMoreInteractions(mockCommandManager); + }); + + test('onCancel should not interact with any dependencies', () { + sut.onCancel(); + + verifyNoMoreInteractions(mockPaint); + verifyNoMoreInteractions(mockCommandFactory); + verifyNoMoreInteractions(mockCommandManager); + }); + }); +} diff --git a/test/widget/tool/eraser_tool_test.dart b/test/widget/tool/eraser_tool_test.dart index d10f480d..6f41b41e 100644 --- a/test/widget/tool/eraser_tool_test.dart +++ b/test/widget/tool/eraser_tool_test.dart @@ -33,14 +33,14 @@ void main() { const testOffset = Offset(10, 10); final pixelColorBeforeErase = await canvasInteractions - .drawLineFromCenter(testOffset) + .dragFromCenter(testOffset) .then((_) => canvasInteractions.getPixelColor(300, 300)); expect(pixelColorBeforeErase, isNot(equals(0))); await bottomNavBarInteractions.selectTool(ToolData.ERASER); final pixelColorAfterErase = await canvasInteractions - .drawLineFromCenter(testOffset) + .dragFromCenter(testOffset) .then((_) => canvasInteractions.getPixelColor(300, 300)); expect(pixelColorAfterErase, equals(0)); }, diff --git a/test/widget/tool/hand_tool_test.dart b/test/widget/tool/hand_tool_test.dart new file mode 100644 index 00000000..0f8edb82 --- /dev/null +++ b/test/widget/tool/hand_tool_test.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:paintroid/service/device_service.dart'; +import 'package:paintroid/tool/tool.dart'; +import 'package:paintroid/ui/pocket_paint.dart'; + +import '../utils/utils.dart'; +import '../utils/interactions/interactive_viewer_interactions.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + late Widget sut; + + setUp(() { + sut = ProviderScope( + overrides: [ + IDeviceService.sizeProvider + .overrideWith((ref) => Future.value(const Size(600, 600))) + ], + child: const MaterialApp( + home: PocketPaint(), + ), + ); + }); + + testWidgets('Pan bottom left', (WidgetTester tester) async { + await tester.pumpWidget(sut); + await tester.pumpAndSettle(); + + final bottomNavBarInteractions = BottomNavBarInteractions(tester); + final interactiveViewerInteractions = InterActiveViewerInteractions(tester); + await bottomNavBarInteractions.selectTool(ToolData.HAND); + await interactiveViewerInteractions.panAndVerify(const Offset(-50, 50)); + }); + + testWidgets('Pan bottom right', (WidgetTester tester) async { + await tester.pumpWidget(sut); + await tester.pumpAndSettle(); + + final bottomNavBarInteractions = BottomNavBarInteractions(tester); + final interactiveViewerInteractions = InterActiveViewerInteractions(tester); + await bottomNavBarInteractions.selectTool(ToolData.HAND); + await interactiveViewerInteractions.panAndVerify(const Offset(50, 50)); + }); + + testWidgets('Pan top left', (WidgetTester tester) async { + await tester.pumpWidget(sut); + await tester.pumpAndSettle(); + + final bottomNavBarInteractions = BottomNavBarInteractions(tester); + final interactiveViewerInteractions = InterActiveViewerInteractions(tester); + await bottomNavBarInteractions.selectTool(ToolData.HAND); + await interactiveViewerInteractions.panAndVerify(const Offset(-50, -50)); + }); + + testWidgets('Pan top right', (WidgetTester tester) async { + await tester.pumpWidget(sut); + await tester.pumpAndSettle(); + + final bottomNavBarInteractions = BottomNavBarInteractions(tester); + final interactiveViewerInteractions = InterActiveViewerInteractions(tester); + await bottomNavBarInteractions.selectTool(ToolData.HAND); + await interactiveViewerInteractions.panAndVerify(const Offset(50, -50)); + }); +} diff --git a/test/widget/utils/interactions/canvas_interactions.dart b/test/widget/utils/interactions/canvas_interactions.dart index effe6b5d..a1fceb54 100644 --- a/test/widget/utils/interactions/canvas_interactions.dart +++ b/test/widget/utils/interactions/canvas_interactions.dart @@ -16,7 +16,7 @@ class CanvasInteractions { List lastOffsets = []; - Future drawLineFromCenter(Offset offset) async { + Future dragFromCenter(Offset offset) async { final drawingCanvasFinder = find.byType(CanvasPainter); expect(drawingCanvasFinder, findsOneWidget); await _tester.drag(drawingCanvasFinder, offset); diff --git a/test/widget/utils/interactions/interactive_viewer_interactions.dart b/test/widget/utils/interactions/interactive_viewer_interactions.dart new file mode 100644 index 00000000..9bff062e --- /dev/null +++ b/test/widget/utils/interactions/interactive_viewer_interactions.dart @@ -0,0 +1,35 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class InterActiveViewerInteractions { + InterActiveViewerInteractions(this._tester); + + double epsilon = 0.1; + + final WidgetTester _tester; + + Future panAndVerify(Offset offset) async { + final finder = find.byType(InteractiveViewer); + + expect(finder, findsOneWidget); + + InteractiveViewer interactiveViewer = _tester.widget(finder); + + TransformationController controller = + interactiveViewer.transformationController!; + + expect(controller, isNotNull); + + final initialMatrix = controller.value; + + await _tester.drag(finder, const Offset(-50, 50)); + await _tester.pumpAndSettle(); + + double expectedX = initialMatrix.getTranslation().x - 50; + double expectedY = initialMatrix.getTranslation().y + 50; + + expect(controller.value.getTranslation().x, closeTo(expectedX, epsilon)); + expect(controller.value.getTranslation().y, closeTo(expectedY, epsilon)); + return this; + } +}