Some checks failed
CI / Windows build (push) Has been cancelled
Replace the ad-hoc tool palette with a shared tool system (EditorToolKind) across the PDF, note and slide editors, and add the core writing tools. - Multiple brushes, each remembering its OWN color (rnote-style): selecting a brush restores its color, changing color updates only that brush, and each brush button shows its current color. - Select tool: tap-select a committed stroke, drag to move it, delete it — persisted and undoable. - Shape tool: line / rectangle / ellipse / arrow, drawn with a live preview and committed as generated PenStrokes (shape_geometry.dart) so they reuse stroke rendering, erase, persistence and undo. - Highlighter + eraser fold into the same tool system. Text/bookmark/search+OCR/backgrounds/Windows-Ink are later batches (TODO). Brush opacity still deferred. analyze clean, 302 tests.
132 lines
5.0 KiB
Dart
132 lines
5.0 KiB
Dart
// test/pen_select_move_test.dart
|
|
//
|
|
// Pins the SELECT tool's select + move + delete on the PenCanvas editors (note):
|
|
// * tapping a committed stroke selects it (onSelectStroke fires with its index);
|
|
// * dragging the selection translates the stroke's normalized points (the
|
|
// committed stroke list is replaced with shifted points);
|
|
// * the move is one undoable step (undo restores the original points).
|
|
//
|
|
// Drives the real PenNoteScreen with a pre-loaded stroke and a stylus gesture.
|
|
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import 'package:badnote/editor/canvas/pen_canvas.dart';
|
|
import 'package:badnote/editor/canvas/pen_note_screen.dart';
|
|
import 'package:badnote/editor/canvas/pen_stroke.dart';
|
|
import 'package:badnote/editor/engine/shape_geometry.dart';
|
|
import 'package:badnote/models/ink_point.dart';
|
|
import 'package:badnote/models/ink_stroke.dart';
|
|
import 'package:badnote/models/note.dart';
|
|
import 'package:badnote/models/pen_tool.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
// A note with a single stroke crossing the page center so a center tap hits it.
|
|
Note noteWithStroke() => Note(
|
|
id: 'n1',
|
|
title: 'Test',
|
|
strokes: [
|
|
InkStroke(
|
|
id: 's1',
|
|
// Through the page center (kNoteLogicalPage = 1000 x 1414) so a
|
|
// center tap on the canvas hits the stroke.
|
|
points: const [
|
|
InkPoint(x: 300, y: 707, timestamp: 0),
|
|
InkPoint(x: 500, y: 707, timestamp: 0),
|
|
InkPoint(x: 700, y: 707, timestamp: 0),
|
|
],
|
|
tool: PenTool.pen,
|
|
createdAt: DateTime.fromMillisecondsSinceEpoch(0),
|
|
),
|
|
],
|
|
createdAt: DateTime.fromMillisecondsSinceEpoch(0),
|
|
updatedAt: DateTime.fromMillisecondsSinceEpoch(0),
|
|
);
|
|
|
|
PenCanvas canvas(WidgetTester tester) =>
|
|
tester.widget<PenCanvas>(find.byType(PenCanvas));
|
|
|
|
List<PenStroke> strokes(WidgetTester tester) => canvas(tester).strokes;
|
|
|
|
/// Activate the SELECT tool via its toolbar button (the "Select" tooltip).
|
|
Future<void> activateSelect(WidgetTester tester) async {
|
|
await tester.tap(find.byTooltip('Select'));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
testWidgets('tapping a stroke selects it, dragging moves it, undo restores',
|
|
(tester) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
await tester.pumpWidget(ProviderScope(
|
|
child: MaterialApp(home: PenNoteScreen(note: noteWithStroke())),
|
|
));
|
|
await tester.pump(); // PenConfig load
|
|
|
|
expect(strokes(tester), hasLength(1));
|
|
final before = strokes(tester).single;
|
|
final beforeBounds = penStrokeBounds(before)!;
|
|
|
|
await activateSelect(tester);
|
|
expect(canvas(tester).tool, CanvasTool.select);
|
|
|
|
// Tap the stroke (page center) to select, then drag it down-right.
|
|
final center = tester.getCenter(find.byType(PenCanvas));
|
|
final g = await tester.startGesture(center, kind: PointerDeviceKind.stylus);
|
|
await tester.pump();
|
|
// selection should now be set.
|
|
expect(canvas(tester).selectedStrokeIndex, 0);
|
|
|
|
await g.moveBy(const Offset(40, 30));
|
|
await g.moveBy(const Offset(20, 10));
|
|
await g.up();
|
|
await tester.pump();
|
|
|
|
final after = strokes(tester).single;
|
|
final afterBounds = penStrokeBounds(after)!;
|
|
// The stroke translated: its bounds shifted right and down.
|
|
expect(afterBounds.left, greaterThan(beforeBounds.left),
|
|
reason: 'stroke moved right');
|
|
expect(afterBounds.top, greaterThan(beforeBounds.top),
|
|
reason: 'stroke moved down');
|
|
// Same number of points (a translate, not a redraw).
|
|
expect(after.points.length, before.points.length);
|
|
|
|
// Undo restores the original position (one undoable step).
|
|
await tester.tap(find.byTooltip('Undo'));
|
|
await tester.pump();
|
|
final undoneBounds = penStrokeBounds(strokes(tester).single)!;
|
|
expect(undoneBounds.left, closeTo(beforeBounds.left, 1e-6));
|
|
expect(undoneBounds.top, closeTo(beforeBounds.top, 1e-6));
|
|
});
|
|
|
|
testWidgets('delete-selection removes the selected stroke (undoable)',
|
|
(tester) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
await tester.pumpWidget(ProviderScope(
|
|
child: MaterialApp(home: PenNoteScreen(note: noteWithStroke())),
|
|
));
|
|
await tester.pump();
|
|
|
|
await activateSelect(tester);
|
|
final center = tester.getCenter(find.byType(PenCanvas));
|
|
final g = await tester.startGesture(center, kind: PointerDeviceKind.stylus);
|
|
await g.up();
|
|
await tester.pump();
|
|
expect(canvas(tester).selectedStrokeIndex, 0);
|
|
|
|
// The delete button appears only with a live selection.
|
|
await tester.tap(find.byTooltip('Delete selection'));
|
|
await tester.pump();
|
|
expect(strokes(tester), isEmpty);
|
|
|
|
await tester.tap(find.byTooltip('Undo'));
|
|
await tester.pump();
|
|
expect(strokes(tester), hasLength(1));
|
|
});
|
|
}
|