Skip to content

Commit

Permalink
PAINTROID-768: Flutter: Add Spray Can tool - tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Lenkomotive committed Oct 3, 2024
1 parent eb01ce0 commit ecb334d
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 9 deletions.
2 changes: 1 addition & 1 deletion lib/core/tools/implementation/spray_tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class SprayTool extends Tool {
super.hasFinalizeFunctionality = false,
});

double sprayRadius = 20.0;
double sprayRadius = 20;
final Random random = Random();

@override
Expand Down
195 changes: 195 additions & 0 deletions test/integration/spray_tool_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
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/app.dart';
import 'package:paintroid/core/tools/implementation/spray_tool.dart';
import 'package:paintroid/core/tools/tool_data.dart';

import '../utils/test_utils.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

const String testIDStr = String.fromEnvironment('id', defaultValue: '-1');
final testID = int.tryParse(testIDStr) ?? testIDStr;

late Widget sut;

setUp(() async {
sut = ProviderScope(
child: App(
showOnboardingPage: false,
),
);
});

if (testID == -1 || testID == 0) {
testWidgets('[SPRAY_TOOL]: test spray at center colors pixels',
(WidgetTester tester) async {
UIInteraction.initialize(tester);
await tester.pumpWidget(sut);
await UIInteraction.createNewImage();
await UIInteraction.selectTool(ToolData.SPRAY.name);

const radius = 50.0;

(UIInteraction.getCurrentTool() as SprayTool).updateSprayRadius(radius);

var color = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
radius: radius.toInt() * 2,
);
expect(color, Colors.transparent);

await UIInteraction.tapAt(CanvasPosition.center, times: 10);

color = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
radius: radius.toInt() * 2,
);

expect(color, isNot(Colors.transparent));
});
}

if (testID == -1 || testID == 1) {
testWidgets('[SPRAY_TOOL]: test spray at top left colors pixels',
(WidgetTester tester) async {
UIInteraction.initialize(tester);
await tester.pumpWidget(sut);
await UIInteraction.createNewImage();
await UIInteraction.selectTool(ToolData.SPRAY.name);

const radius = 50.0;

(UIInteraction.getCurrentTool() as SprayTool).updateSprayRadius(radius);

var color = await UIInteraction.getPixelColor(
CanvasPosition.left,
CanvasPosition.top,
radius: radius.toInt() * 2,
);
expect(color, Colors.transparent);

await UIInteraction.tapAt(CanvasPosition.topLeft, times: 10);

color = await UIInteraction.getPixelColor(
CanvasPosition.left,
CanvasPosition.top,
radius: radius.toInt() * 2,
);

expect(color, isNot(Colors.transparent));
});
}

if (testID == -1 || testID == 2) {
testWidgets('[SPRAY_TOOL]: test spray at bottom right colors pixels',
(WidgetTester tester) async {
UIInteraction.initialize(tester);
await tester.pumpWidget(sut);
await UIInteraction.createNewImage();
await UIInteraction.selectTool(ToolData.SPRAY.name);

const radius = 50.0;

(UIInteraction.getCurrentTool() as SprayTool).updateSprayRadius(radius);

var color = await UIInteraction.getPixelColor(
CanvasPosition.right,
CanvasPosition.bottom,
radius: radius.toInt() * 2,
);
expect(color, Colors.transparent);

await UIInteraction.tapAt(CanvasPosition.bottomRight, times: 10);

color = await UIInteraction.getPixelColor(
CanvasPosition.right,
CanvasPosition.bottom,
radius: radius.toInt() * 2,
);

expect(color, isNot(Colors.transparent));
});
}

if (testID == -1 || testID == 3) {
testWidgets('[SPRAY_TOOL]: test spray with drag colors pixels along path',
(WidgetTester tester) async {
UIInteraction.initialize(tester);
await tester.pumpWidget(sut);
await UIInteraction.createNewImage();
await UIInteraction.selectTool(ToolData.SPRAY.name);

const radius = 50.0;

(UIInteraction.getCurrentTool() as SprayTool).updateSprayRadius(radius);

var color = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
radius: radius.toInt() * 2,
);
expect(color, Colors.transparent);

await UIInteraction.dragFromTo(
CanvasPosition.topLeft,
CanvasPosition.bottomRight,
steps: 500,
);

color = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
radius: radius.toInt() * 2,
);

expect(color, isNot(Colors.transparent));
});
}

if (testID == -1 || testID == 5) {
testWidgets('[SPRAY_TOOL]: test spray undo and redo',
(WidgetTester tester) async {
UIInteraction.initialize(tester);
await tester.pumpWidget(sut);
await UIInteraction.createNewImage();
await UIInteraction.selectTool(ToolData.SPRAY.name);

const radius = 50.0;

(UIInteraction.getCurrentTool() as SprayTool).updateSprayRadius(radius);

await UIInteraction.tapAt(CanvasPosition.center, times: 10);

var color = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
radius: radius.toInt() * 2,
);
expect(color, isNot(Colors.transparent));

await UIInteraction.clickUndo(times: 10);

color = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
radius: radius.toInt() * 2,
);
expect(color, Colors.transparent);

await UIInteraction.clickRedo(times: 10);

color = await UIInteraction.getPixelColor(
CanvasPosition.centerX,
CanvasPosition.centerY,
radius: radius.toInt() * 2,
);
expect(color, isNot(Colors.transparent));
});
}
}
105 changes: 105 additions & 0 deletions test/unit/tools/spray_tool_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import 'dart:ui';

import 'package:flutter_test/flutter_test.dart';
import 'package:paintroid/core/commands/command_factory/command_factory.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/spray_command.dart';
import 'package:paintroid/core/commands/command_manager/command_manager.dart';
import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart';
import 'package:paintroid/core/enums/tool_types.dart';
import 'package:paintroid/core/tools/implementation/spray_tool.dart';

void main() {
late SprayTool sut;

const Offset pointA = Offset(100, 100);
const Offset pointB = Offset(200, 200);

Paint paint = Paint();

setUp(() {
sut = SprayTool(
commandFactory: const CommandFactory(),
commandManager: CommandManager(),
graphicFactory: const GraphicFactory(),
type: ToolType.SPRAY,
drawingSurfaceSize: const Size(1000, 1000),
);
});

group('On tap down event', () {
test('Should create one SprayCommand with initial points', () {
expect(sut.commandManager.undoStack.isEmpty, true);
sut.onDown(pointA, paint);
expect(sut.commandManager.undoStack.length, 1);
expect(sut.commandManager.undoStack.first is SprayCommand, true);
final sprayCommand = sut.commandManager.undoStack.first as SprayCommand;
expect(sprayCommand.points.isNotEmpty, true);
});

test('SprayCommand points should increase on onDrag', () {
sut.onDown(pointA, paint);
final sprayCommand = sut.commandManager.undoStack.first as SprayCommand;
int initialPointCount = sprayCommand.points.length;
sut.onDrag(pointB, paint);
expect(sprayCommand.points.length, greaterThan(initialPointCount));
});

test('Generated spray points should be within sprayRadius * 2', () {
sut.updateSprayRadius(50.0);
sut.onDown(pointA, paint);
final sprayCommand = sut.commandManager.undoStack.first as SprayCommand;
for (var point in sprayCommand.points) {
double distance = (point - pointA).distance;
expect(distance, lessThanOrEqualTo(50.0 * 2));
}
});

test('On tap up, no additional commands are created', () {
sut.onDown(pointA, paint);
sut.onUp(pointA, paint);
expect(sut.commandManager.undoStack.length, 1);
});
});

group('SprayTool properties and methods', () {
test('Should return SPRAY as ToolType', () {
expect(sut.type, ToolType.SPRAY);
});

test('updateSprayRadius should change sprayRadius', () {
double initialRadius = sut.sprayRadius;
sut.updateSprayRadius(80.0);
expect(sut.sprayRadius, equals(80.0));
expect(sut.sprayRadius, isNot(equals(initialRadius)));
});

test('Spray density adjusts with sprayRadius', () {
sut.updateSprayRadius(30.0);
sut.onDown(pointA, paint);
final sprayCommand1 = sut.commandManager.undoStack.last as SprayCommand;
int pointCount30 = sprayCommand1.points.length;

sut.updateSprayRadius(60.0);
sut.onDown(pointB, paint);
final sprayCommand2 = sut.commandManager.undoStack.last as SprayCommand;
int pointCount60 = sprayCommand2.points.length;

expect(pointCount60, greaterThan(pointCount30));
});

test('onCancel discards the last command', () {
sut.onDown(pointA, paint);
expect(sut.commandManager.undoStack.length, 1);
sut.onCancel();
expect(sut.commandManager.undoStack.isEmpty, true);
});

test('onUndo removes the last command from the undoStack', () {
sut.onDown(pointA, paint);
sut.onUp(pointA, paint);
expect(sut.commandManager.undoStack.length, 1);
sut.onUndo();
expect(sut.commandManager.undoStack.length, 0);
});
});
}
47 changes: 39 additions & 8 deletions test/utils/ui_interaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class UIInteraction {
return (leftPixel, rightPixel, topPixel, bottomPixel);
}

static Future<Color> getPixelColor(int x, int y) async {
static Future<Color> getPixelColor(int x, int y, {int radius = 0}) async {
final container =
ProviderScope.containerOf(tester.element(find.byType(App)));
final canvasStateNotifier = container.read(canvasStateProvider.notifier);
Expand All @@ -89,15 +89,35 @@ class UIInteraction {
final rawBytes = byteData.buffer.asUint8List();
final image =
img.Image.fromBytes(cachedImage.width, cachedImage.height, rawBytes);
var pixel = image.getPixel(x, y);

if (radius != 0) {
for (int i = x - radius; i <= x + radius; i++) {
for (int j = y - radius; j <= y + radius; j++) {
if (i < 0 || i >= image.width || j < 0 || j >= image.height) {
continue;
}
final argbColor = getColorAtPixel(image, x, y);
if (argbColor != 0) {
return Color(argbColor);
}
}
}
return Colors.transparent;
}

final argbColor = getColorAtPixel(image, x, y);
return Color(argbColor);
}

static int getColorAtPixel(img.Image image, int x, int y) {
var pixel = image.getPixel(x, y);
final a = img.getAlpha(pixel);
final r = img.getRed(pixel);
final g = img.getGreen(pixel);
final b = img.getBlue(pixel);

final argbColor = (a << 24) | (r << 16) | (g << 8) | b;
return Color(argbColor);
return argbColor;
}

static Future<void> createNewImage() async {
Expand Down Expand Up @@ -189,19 +209,30 @@ class UIInteraction {
}
}

static Future<void> dragFromTo(Offset from, Offset to) async {
static Future<void> dragFromTo(
Offset from,
Offset to, {
int steps = 1,
}) async {
final TestGesture gesture = await tester.startGesture(from);
await tester.pumpAndSettle(const Duration(milliseconds: 500));

await gesture.moveTo(to);
await tester.pumpAndSettle(const Duration(milliseconds: 500));
final dx = (to.dx - from.dx) / steps;
final dy = (to.dy - from.dy) / steps;

for (int i = 1; i <= steps; i++) {
final Offset nextPoint = Offset(from.dx + dx * i, from.dy + dy * i);
await gesture.moveTo(nextPoint);
await tester.pumpAndSettle(const Duration(milliseconds: 16));
}
await gesture.up();
await tester.pumpAndSettle();
}

static Future<void> tapAt(Offset position) async {
await tester.tapAt(position);
static Future<void> tapAt(Offset position, {int times = 0}) async {
for (var i = 0; i <= times; i++) {
await tester.tapAt(position);
}
await tester.pumpAndSettle();
}

Expand Down

0 comments on commit ecb334d

Please sign in to comment.