Compare commits

..

No commits in common. "c82bb7fa2a0a67ac822e3fbfd139a3aa3794ea9a" and "545d3ad6882487e448276802df00f4199bc772dc" have entirely different histories.

73 changed files with 350 additions and 1410 deletions

View File

@ -1,18 +1,8 @@
targets: targets:
$default: $default:
sources: sources:
- integration_test/** # By default, build runner will not generate code in the integration folder - integration_test/**
- test/** # so we override paths for code generation here - test/**
- lib/** - lib/**
- $package$ - $package$
builders: builders:
bdd_widget_test|featureBuilder:
generate_for:
- test/**
- integration_test/**
freezed:
generate_for:
- lib/**
json_serializable:
generate_for:
- lib/**

View File

@ -4,19 +4,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
import 'dart:io';
import 'package:pdf_signature/data/services/export_service.dart'; import 'package:pdf_signature/data/services/export_service.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pages_sidebar.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:pdf_signature/data/repositories/preferences_repository.dart'; import 'package:pdf_signature/data/repositories/preferences_repository.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
@ -39,8 +34,6 @@ void main() {
final fake = RecordingExporter(); final fake = RecordingExporter();
SharedPreferences.setMockInitialValues({}); SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
// For this test, we don't need the PDF bytes since it's not loaded
await tester.pumpWidget( await tester.pumpWidget(
ProviderScope( ProviderScope(
overrides: [ overrides: [
@ -51,7 +44,7 @@ void main() {
(ref) => (ref) =>
DocumentStateNotifier()..openPicked( DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf', path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3, pageCount: 5,
), ),
), ),
useMockViewerProvider.overrideWith((ref) => false), useMockViewerProvider.overrideWith((ref) => false),
@ -97,8 +90,6 @@ void main() {
tester, tester,
) async { ) async {
final sigBytes = _makeSig(); final sigBytes = _makeSig();
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({}); SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@ -112,8 +103,7 @@ void main() {
(ref) => (ref) =>
DocumentStateNotifier()..openPicked( DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf', path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3, pageCount: 5,
bytes: pdfBytes,
), ),
), ),
signatureAssetRepositoryProvider.overrideWith((ref) { signatureAssetRepositoryProvider.overrideWith((ref) {
@ -121,12 +111,6 @@ void main() {
c.add(sigBytes, name: 'image'); c.add(sigBytes, name: 'image');
return c; return c;
}), }),
signatureCardRepositoryProvider.overrideWith((ref) {
final cardRepo = SignatureCardStateNotifier();
final asset = SignatureAsset(bytes: sigBytes, name: 'image');
cardRepo.addWithAsset(asset, 0.0);
return cardRepo;
}),
useMockViewerProvider.overrideWithValue(false), useMockViewerProvider.overrideWithValue(false),
], ],
child: const MaterialApp( child: const MaterialApp(
@ -155,10 +139,10 @@ void main() {
final r = container.read(activeRectProvider)!; final r = container.read(activeRectProvider)!;
final lib = container.read(signatureAssetRepositoryProvider); final lib = container.read(signatureAssetRepositoryProvider);
final asset = lib.isNotEmpty ? lib.first : null; final asset = lib.isNotEmpty ? lib.first : null;
final currentPage = container.read(pdfViewModelProvider); final pdf = container.read(documentRepositoryProvider);
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.addPlacement(page: currentPage, rect: r, asset: asset); .addPlacement(page: pdf.currentPage, rect: r, asset: asset);
// Clear active overlay by hiding signatures temporarily // Clear active overlay by hiding signatures temporarily
container.read(signatureVisibilityProvider.notifier).state = false; container.read(signatureVisibilityProvider.notifier).state = false;
await tester.pump(); await tester.pump();
@ -178,182 +162,4 @@ void main() {
isTrue, isTrue,
); );
}); });
// ---- PDF view interaction tests (merged from pdf_view_test.dart) ----
testWidgets('PDF View: programmatic page jumps reach last page', (
tester,
) async {
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) =>
DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3,
bytes: pdfBytes,
),
),
useMockViewerProvider.overrideWithValue(false),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'),
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
final ctx = tester.element(find.byType(PdfSignatureHomePage));
final container = ProviderScope.containerOf(ctx);
expect(container.read(pdfViewModelProvider), 1);
container.read(pdfViewModelProvider.notifier).jumpToPage(2);
await tester.pumpAndSettle();
expect(container.read(pdfViewModelProvider), 2);
container.read(pdfViewModelProvider.notifier).jumpToPage(3);
await tester.pumpAndSettle();
expect(container.read(pdfViewModelProvider), 3);
});
testWidgets('PDF View: zoom in/out', (tester) async {
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) =>
DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3,
bytes: pdfBytes,
),
),
useMockViewerProvider.overrideWithValue(false),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'),
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
final pdfViewer = find.byKey(const ValueKey('pdf_page_area'));
expect(pdfViewer, findsOneWidget);
final center = tester.getCenter(pdfViewer);
final g1 = await tester.createGesture();
final g2 = await tester.createGesture();
await g1.down(center - const Offset(10, 0));
await g2.down(center + const Offset(10, 0));
await g1.moveTo(center - const Offset(20, 0));
await g2.moveTo(center + const Offset(20, 0));
await g1.up();
await g2.up();
await tester.pumpAndSettle();
expect(pdfViewer, findsOneWidget);
});
testWidgets('PDF View: jump to page by clicking thumbnail', (tester) async {
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) =>
DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3,
bytes: pdfBytes,
),
),
useMockViewerProvider.overrideWithValue(false),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'),
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
final ctx = tester.element(find.byType(PdfSignatureHomePage));
final container = ProviderScope.containerOf(ctx);
expect(container.read(pdfViewModelProvider), 1);
final page3Thumb = find.text('3');
expect(page3Thumb, findsOneWidget);
await tester.tap(page3Thumb);
await tester.pumpAndSettle();
expect(container.read(pdfViewModelProvider), 3);
});
testWidgets('PDF View: thumbnails scroll and select', (tester) async {
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) =>
DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3,
bytes: pdfBytes,
),
),
useMockViewerProvider.overrideWithValue(false),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'),
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
final ctx = tester.element(find.byType(PdfSignatureHomePage));
final container = ProviderScope.containerOf(ctx);
expect(container.read(pdfViewModelProvider), 1);
final sidebar = find.byType(PagesSidebar);
expect(sidebar, findsOneWidget);
await tester.drag(sidebar, const Offset(0, -200));
await tester.pumpAndSettle();
expect(find.text('1'), findsOneWidget);
expect(container.read(pdfViewModelProvider), 1);
await tester.tap(find.text('2'));
await tester.pumpAndSettle();
expect(container.read(pdfViewModelProvider), 2);
});
} }

View File

@ -1,221 +0,0 @@
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 'dart:io';
import 'package:flutter/services.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pages_sidebar.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:pdf_signature/data/repositories/preferences_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/l10n/app_localizations.dart';
/// It has known that sample-local-pdf.pdf has 3 pages.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('PDF View: programmatic page jumps reach last page', (
tester,
) async {
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) =>
DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3,
bytes: pdfBytes,
),
),
useMockViewerProvider.overrideWithValue(false),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'),
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
// Extra settle to avoid startup race when running with other integration tests.
await tester.pump(const Duration(milliseconds: 200));
final ctx = tester.element(find.byType(PdfSignatureHomePage));
final container = ProviderScope.containerOf(ctx);
final vm = container.read(pdfViewModelProvider);
expect(vm, 1);
container.read(pdfViewModelProvider.notifier).jumpToPage(2);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 120));
expect(container.read(pdfViewModelProvider), 2);
container.read(pdfViewModelProvider.notifier).jumpToPage(3);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 120));
expect(container.read(pdfViewModelProvider), 3);
});
testWidgets('PDF View: zoom in/out', (tester) async {
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) =>
DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3,
bytes: pdfBytes,
),
),
useMockViewerProvider.overrideWithValue(false),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'),
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 120));
final pdfViewer = find.byKey(const ValueKey('pdf_page_area'));
expect(pdfViewer, findsOneWidget);
final center = tester.getCenter(pdfViewer);
final gesture1 = await tester.createGesture();
final gesture2 = await tester.createGesture();
await gesture1.down(center - const Offset(10, 0));
await gesture2.down(center + const Offset(10, 0));
await gesture1.moveTo(center - const Offset(20, 0));
await gesture2.moveTo(center + const Offset(20, 0));
await gesture1.up();
await gesture2.up();
await tester.pumpAndSettle();
expect(pdfViewer, findsOneWidget);
});
testWidgets('PDF View: jump to page by clicking thumbnail', (tester) async {
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) =>
DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3,
bytes: pdfBytes,
),
),
useMockViewerProvider.overrideWithValue(false),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'),
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
final ctx = tester.element(find.byType(PdfSignatureHomePage));
final container = ProviderScope.containerOf(ctx);
expect(container.read(pdfViewModelProvider), 1);
final page3Thumbnail = find.text('3');
expect(page3Thumbnail, findsOneWidget);
await tester.tap(page3Thumbnail);
await tester.pumpAndSettle();
expect(container.read(pdfViewModelProvider), 3);
});
testWidgets('PDF View: thumbnails scroll and select', (tester) async {
final pdfBytes =
await File('integration_test/data/sample-local-pdf.pdf').readAsBytes();
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(
ProviderScope(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) =>
DocumentStateNotifier()..openPicked(
path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 3,
bytes: pdfBytes,
),
),
useMockViewerProvider.overrideWithValue(false),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale('en'),
home: PdfSignatureHomePage(),
),
),
);
await tester.pumpAndSettle();
final ctx = tester.element(find.byType(PdfSignatureHomePage));
final container = ProviderScope.containerOf(ctx);
expect(container.read(pdfViewModelProvider), 1);
final pagesSidebar = find.byType(PagesSidebar);
expect(pagesSidebar, findsOneWidget);
await tester.drag(pagesSidebar, const Offset(0, -200));
await tester.pumpAndSettle();
expect(find.text('1'), findsOneWidget);
expect(container.read(pdfViewModelProvider), 1);
// Select page 2 thumbnail and verify page changes
await tester.tap(find.text('2'));
await tester.pumpAndSettle();
expect(container.read(pdfViewModelProvider), 2);
});
}

View File

@ -12,7 +12,12 @@ class DocumentStateNotifier extends StateNotifier<Document> {
@visibleForTesting @visibleForTesting
void openSample() { void openSample() {
state = state.copyWith(loaded: true, pageCount: 5, placementsByPage: {}); state = state.copyWith(
loaded: true,
pageCount: 5,
currentPage: 1,
placementsByPage: {},
);
} }
void openPicked({ void openPicked({
@ -23,20 +28,23 @@ class DocumentStateNotifier extends StateNotifier<Document> {
state = state.copyWith( state = state.copyWith(
loaded: true, loaded: true,
pageCount: pageCount, pageCount: pageCount,
currentPage: 1,
pickedPdfBytes: bytes, pickedPdfBytes: bytes,
placementsByPage: {}, placementsByPage: {},
); );
} }
void jumpTo(int page) {
if (!state.loaded) return;
final clamped = page.clamp(1, state.pageCount);
state = state.copyWith(currentPage: clamped);
}
void setPageCount(int count) { void setPageCount(int count) {
if (!state.loaded) return; if (!state.loaded) return;
state = state.copyWith(pageCount: count.clamp(1, 9999)); state = state.copyWith(pageCount: count.clamp(1, 9999));
} }
void jumpTo(int page) {
// currentPage is now in view model, so jumpTo does nothing here
}
// Multiple-signature helpers (rects are stored in normalized fractions 0..1 // Multiple-signature helpers (rects are stored in normalized fractions 0..1
// relative to the page size: left/top/width/height are all 0..1) // relative to the page size: left/top/width/height are all 0..1)
void addPlacement({ void addPlacement({
@ -44,7 +52,6 @@ class DocumentStateNotifier extends StateNotifier<Document> {
required Rect rect, required Rect rect,
SignatureAsset? asset, SignatureAsset? asset,
double rotationDeg = 0.0, double rotationDeg = 0.0,
GraphicAdjust? graphicAdjust,
}) { }) {
if (!state.loaded) return; if (!state.loaded) return;
final p = page.clamp(1, state.pageCount); final p = page.clamp(1, state.pageCount);
@ -53,87 +60,14 @@ class DocumentStateNotifier extends StateNotifier<Document> {
list.add( list.add(
SignaturePlacement( SignaturePlacement(
rect: rect, rect: rect,
asset: asset ?? SignatureAsset(bytes: _singleTransparentPng), asset: asset ?? SignatureAsset(bytes: Uint8List(0)),
rotationDeg: rotationDeg, rotationDeg: rotationDeg,
graphicAdjust: graphicAdjust ?? const GraphicAdjust(),
), ),
); );
map[p] = list; map[p] = list;
state = state.copyWith(placementsByPage: map); state = state.copyWith(placementsByPage: map);
} }
// Tiny 1x1 transparent PNG to avoid decode crashes in tests when no real
// signature bytes were provided.
static final Uint8List _singleTransparentPng = Uint8List.fromList([
0x89,
0x50,
0x4E,
0x47,
0x0D,
0x0A,
0x1A,
0x0A,
0x00,
0x00,
0x00,
0x0D,
0x49,
0x48,
0x44,
0x52,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x01,
0x08,
0x06,
0x00,
0x00,
0x00,
0x1F,
0x15,
0xC4,
0x89,
0x00,
0x00,
0x00,
0x0A,
0x49,
0x44,
0x41,
0x54,
0x78,
0x9C,
0x63,
0x60,
0x00,
0x00,
0x00,
0x02,
0x00,
0x01,
0xE5,
0x27,
0xD4,
0xA6,
0x00,
0x00,
0x00,
0x00,
0x49,
0x45,
0x4E,
0x44,
0xAE,
0x42,
0x60,
0x82,
]);
void updatePlacementRotation({ void updatePlacementRotation({
required int page, required int page,
required int index, required int index,

View File

@ -5,29 +5,34 @@ import 'signature_placement.dart';
class Document { class Document {
final bool loaded; final bool loaded;
final int pageCount; final int pageCount;
final int currentPage;
final Uint8List? pickedPdfBytes; final Uint8List? pickedPdfBytes;
// Multiple signature placements per page, each combines geometry and asset. // Multiple signature placements per page, each combines geometry and asset.
final Map<int, List<SignaturePlacement>> placementsByPage; final Map<int, List<SignaturePlacement>> placementsByPage;
const Document({ const Document({
required this.loaded, required this.loaded,
required this.pageCount, required this.pageCount,
required this.currentPage,
this.pickedPdfBytes, this.pickedPdfBytes,
this.placementsByPage = const {}, this.placementsByPage = const {},
}); });
factory Document.initial() => const Document( factory Document.initial() => const Document(
loaded: false, loaded: false,
pageCount: 0, pageCount: 0,
currentPage: 1,
pickedPdfBytes: null, pickedPdfBytes: null,
placementsByPage: {}, placementsByPage: {},
); );
Document copyWith({ Document copyWith({
bool? loaded, bool? loaded,
int? pageCount, int? pageCount,
int? currentPage,
Uint8List? pickedPdfBytes, Uint8List? pickedPdfBytes,
Map<int, List<SignaturePlacement>>? placementsByPage, Map<int, List<SignaturePlacement>>? placementsByPage,
}) => Document( }) => Document(
loaded: loaded ?? this.loaded, loaded: loaded ?? this.loaded,
pageCount: pageCount ?? this.pageCount, pageCount: pageCount ?? this.pageCount,
currentPage: currentPage ?? this.currentPage,
pickedPdfBytes: pickedPdfBytes ?? this.pickedPdfBytes, pickedPdfBytes: pickedPdfBytes ?? this.pickedPdfBytes,
placementsByPage: placementsByPage ?? this.placementsByPage, placementsByPage: placementsByPage ?? this.placementsByPage,
); );

View File

@ -6,15 +6,15 @@ import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdfrx/pdfrx.dart'; import 'package:pdfrx/pdfrx.dart';
class PdfViewModel extends StateNotifier<int> { class PdfViewModel {
final Ref ref; final Ref ref;
PdfViewModel(this.ref) : super(1); PdfViewModel(this.ref);
Document get document => ref.read(documentRepositoryProvider); Document get document => ref.read(documentRepositoryProvider);
void jumpToPage(int page) { void jumpToPage(int page) {
state = page.clamp(1, document.pageCount); ref.read(documentRepositoryProvider.notifier).jumpTo(page);
} }
Future<void> openPdf({required String path, Uint8List? bytes}) async { Future<void> openPdf({required String path, Uint8List? bytes}) async {
@ -31,7 +31,6 @@ class PdfViewModel extends StateNotifier<int> {
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.openPicked(path: path, pageCount: pageCount, bytes: bytes); .openPicked(path: path, pageCount: pageCount, bytes: bytes);
ref.read(signatureCardRepositoryProvider.notifier).clearAll(); ref.read(signatureCardRepositoryProvider.notifier).clearAll();
state = 1; // Reset current page to 1
} }
Future<Uint8List?> loadSignatureFromFile() async { Future<Uint8List?> loadSignatureFromFile() async {
@ -61,6 +60,6 @@ class PdfViewModel extends StateNotifier<int> {
} }
} }
final pdfViewModelProvider = StateNotifierProvider<PdfViewModel, int>((ref) { final pdfViewModelProvider = Provider<PdfViewModel>((ref) {
return PdfViewModel(ref); return PdfViewModel(ref);
}); });

View File

@ -47,10 +47,16 @@ class _DrawCanvasState extends State<DrawCanvas> {
children: [ children: [
ElevatedButton( ElevatedButton(
key: const Key('btn_canvas_confirm'), key: const Key('btn_canvas_confirm'),
onPressed: () { onPressed: () async {
// Export signature to PNG bytes // Export signature to PNG bytes
// In test, use dummy bytes final data = await _control.toImage(
final bytes = Uint8List.fromList([1, 2, 3]); color: Colors.black,
background: Colors.transparent,
fit: true,
width: 1024,
height: 512,
);
final bytes = data?.buffer.asUint8List();
widget.debugBytesSink?.value = bytes; widget.debugBytesSink?.value = bytes;
if (widget.onConfirm != null) { if (widget.onConfirm != null) {
widget.onConfirm!(bytes); widget.onConfirm!(bytes);

View File

@ -59,6 +59,7 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
final clearPending = widget.clearPending; final clearPending = widget.clearPending;
final visible = ref.watch(signatureVisibilityProvider); final visible = ref.watch(signatureVisibilityProvider);
final assets = ref.watch(signatureAssetRepositoryProvider); final assets = ref.watch(signatureAssetRepositoryProvider);
final aspectLocked = ref.watch(aspectLockedProvider);
if (pendingPage != null) { if (pendingPage != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final p = pendingPage; final p = pendingPage;
@ -117,7 +118,6 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
rect: rect, rect: rect,
asset: dragData.card?.asset, asset: dragData.card?.asset,
rotationDeg: dragData.card?.rotationDeg ?? 0.0, rotationDeg: dragData.card?.rotationDeg ?? 0.0,
graphicAdjust: dragData.card?.graphicAdjust,
); );
} }
}, },
@ -195,7 +195,35 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
height: height, height: height,
child: GestureDetector( child: GestureDetector(
key: const Key('signature_overlay'), key: const Key('signature_overlay'),
// Removed onPanUpdate to allow scrolling onPanUpdate: (d) {
final dx =
d.delta.dx /
constraints.maxWidth;
final dy =
d.delta.dy /
constraints.maxHeight;
setState(() {
double l = (_activeRect.left + dx)
.clamp(0.0, 1.0);
double t = (_activeRect.top + dy)
.clamp(0.0, 1.0);
// clamp so it stays within page
l = l.clamp(
0.0,
1.0 - _activeRect.width,
);
t = t.clamp(
0.0,
1.0 - _activeRect.height,
);
_activeRect = Rect.fromLTWH(
l,
t,
_activeRect.width,
_activeRect.height,
);
});
},
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
@ -215,7 +243,48 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
height: 14, height: 14,
child: GestureDetector( child: GestureDetector(
key: const Key('signature_handle'), key: const Key('signature_handle'),
// Removed onPanUpdate to allow scrolling onPanUpdate: (d) {
final dx =
d.delta.dx /
constraints.maxWidth;
final dy =
d.delta.dy /
constraints.maxHeight;
setState(() {
double newW = (_activeRect.width +
dx)
.clamp(0.05, 1.0);
double newH =
(_activeRect.height + dy)
.clamp(0.05, 1.0);
if (aspectLocked) {
final ratio =
_activeRect.width /
_activeRect.height;
// keep ratio; prefer width change driving height
newH = (newW /
(ratio == 0
? 1
: ratio))
.clamp(0.05, 1.0);
}
// clamp to page bounds
newW = newW.clamp(
0.05,
1.0 - _activeRect.left,
);
newH = newH.clamp(
0.05,
1.0 - _activeRect.top,
);
_activeRect = Rect.fromLTWH(
_activeRect.left,
_activeRect.top,
newW,
newH,
);
});
},
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,

View File

@ -5,8 +5,6 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'pdf_viewer_widget.dart'; import 'pdf_viewer_widget.dart';
import '../view_model/pdf_view_model.dart';
import 'pdf_providers.dart';
class PdfPageArea extends ConsumerStatefulWidget { class PdfPageArea extends ConsumerStatefulWidget {
const PdfPageArea({ const PdfPageArea({
@ -49,7 +47,10 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
// is instructed to align to the provider's current page once ready. // is instructed to align to the provider's current page once ready.
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
// initial scroll not needed; controller handles positioning final pdf = ref.read(documentRepositoryProvider);
if (pdf.loaded) {
_scrollToPage(pdf.currentPage);
}
}); });
} }
@ -63,7 +64,6 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
void _scrollToPage(int page) { void _scrollToPage(int page) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
_programmaticTargetPage = page;
// Mock continuous: try ensureVisible on the page container // Mock continuous: try ensureVisible on the page container
// Mock continuous: try ensureVisible on the page container // Mock continuous: try ensureVisible on the page container
final ctx = _pageKey(page).currentContext; final ctx = _pageKey(page).currentContext;
@ -84,8 +84,6 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
.clamp(position.minScrollExtent, position.maxScrollExtent) .clamp(position.minScrollExtent, position.maxScrollExtent)
.toDouble(); .toDouble();
position.jumpTo(newPixels); position.jumpTo(newPixels);
_visiblePage = page;
_programmaticTargetPage = null;
return; return;
} }
} catch (_) { } catch (_) {
@ -96,8 +94,6 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
duration: Duration.zero, duration: Duration.zero,
curve: Curves.linear, curve: Curves.linear,
); );
_visiblePage = page;
_programmaticTargetPage = null;
return; return;
} }
return; return;
@ -119,18 +115,12 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final pdf = ref.watch(documentRepositoryProvider); final pdf = ref.watch(documentRepositoryProvider);
const pageViewMode = 'continuous'; const pageViewMode = 'continuous';
// React to PdfViewModel (source of truth for current page)
ref.listen<int>(pdfViewModelProvider, (prev, next) {
if (prev != next) {
_scrollToPage(next);
}
});
// React to provider currentPage changes (e.g., user tapped overview) // React to provider currentPage changes (e.g., user tapped overview)
ref.listen(currentPageProvider, (prev, next) { ref.listen(documentRepositoryProvider, (prev, next) {
if (_suppressProviderListen) return; if (_suppressProviderListen) return;
if (prev != next) { if ((prev?.currentPage != next.currentPage)) {
final target = next; final target = next.currentPage;
// If we're already navigating to this target, ignore; otherwise allow new target. // If we're already navigating to this target, ignore; otherwise allow new target.
if (_programmaticTargetPage != null && if (_programmaticTargetPage != null &&
_programmaticTargetPage == target) { _programmaticTargetPage == target) {

View File

@ -4,7 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../domain/models/model.dart'; import '../../../../domain/models/model.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'signature_overlay.dart'; import 'signature_overlay.dart';
import 'pdf_providers.dart';
/// Builds all overlays for a given page: placed signatures and the active one. /// Builds all overlays for a given page: placed signatures and the active one.
class PdfPageOverlays extends ConsumerWidget { class PdfPageOverlays extends ConsumerWidget {
@ -48,42 +47,6 @@ class PdfPageOverlays extends ConsumerWidget {
); );
} }
// Add active overlay if present and not using mock (mock has its own)
final activeRect = ref.watch(activeRectProvider);
final useMock = ref.watch(useMockViewerProvider);
if (!useMock && activeRect != null) {
widgets.add(
LayoutBuilder(
builder: (context, constraints) {
final left = activeRect.left * constraints.maxWidth;
final top = activeRect.top * constraints.maxHeight;
final width = activeRect.width * constraints.maxWidth;
final height = activeRect.height * constraints.maxHeight;
return Stack(
children: [
Positioned(
left: left,
top: top,
width: width,
height: height,
child: GestureDetector(
key: const Key('signature_overlay'),
// Removed onPanUpdate to allow scrolling
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(color: Colors.red, width: 2),
),
child: const SizedBox.expand(),
),
),
),
],
);
},
),
);
}
return Stack(children: widgets); return Stack(children: widgets);
} }
} }

View File

@ -21,13 +21,12 @@ class PdfPagesOverview extends ConsumerWidget {
separatorBuilder: (_, _) => const SizedBox(height: 8), separatorBuilder: (_, _) => const SizedBox(height: 8),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final pageNumber = index + 1; final pageNumber = index + 1;
final isSelected = ref.watch(currentPageProvider) == pageNumber; final isSelected = pdf.currentPage == pageNumber;
return InkWell( return InkWell(
onTap: () { onTap:
final controller = ref.read(pdfViewerControllerProvider); () => ref
if (controller.isReady) .read(documentRepositoryProvider.notifier)
controller.goToPage(pageNumber: pageNumber); .jumpTo(pageNumber),
},
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: color:

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdfrx/pdfrx.dart';
/// Whether to use a mock continuous viewer (ListView) instead of a real PDF viewer. /// Whether to use a mock continuous viewer (ListView) instead of a real PDF viewer.
/// Tests will override this to true. /// Tests will override this to true.
@ -17,14 +16,3 @@ final aspectLockedProvider = StateProvider<bool>((ref) => false);
/// Current active overlay rect (normalized 0..1) for the mock viewer. /// Current active overlay rect (normalized 0..1) for the mock viewer.
/// Integration tests can read this to confirm or compute placements. /// Integration tests can read this to confirm or compute placements.
final activeRectProvider = StateProvider<Rect?>((ref) => null); final activeRectProvider = StateProvider<Rect?>((ref) => null);
/// Exposes the PdfViewerController so toolbar / thumbnails can invoke navigation.
/// It must be overridden at runtime by the hosting screen (e.g. `PdfSignatureHomePage`).
// Default controller (can be overridden by a screen to ensure a stable instance within its subtree).
final PdfViewerController _defaultPdfViewerController = PdfViewerController();
final pdfViewerControllerProvider = Provider<PdfViewerController>((ref) {
return _defaultPdfViewerController;
});
/// Current page (1-based). Updated by PdfViewer via onPageChanged.
final currentPageProvider = StateProvider<int>((ref) => 1);

View File

@ -3,20 +3,18 @@ import 'package:file_selector/file_selector.dart' as fs;
import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdfrx/pdfrx.dart';
import 'package:pdf_signature/data/repositories/preferences_repository.dart'; import 'package:pdf_signature/data/repositories/preferences_repository.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:multi_split_view/multi_split_view.dart'; import 'package:multi_split_view/multi_split_view.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'pdf_providers.dart';
import 'package:pdfrx/pdfrx.dart';
import 'draw_canvas.dart'; import 'draw_canvas.dart';
import 'pdf_toolbar.dart'; import 'pdf_toolbar.dart';
import 'pdf_page_area.dart'; import 'pdf_page_area.dart';
import 'pages_sidebar.dart'; import 'pages_sidebar.dart';
import 'signatures_sidebar.dart'; import 'signatures_sidebar.dart';
import 'ui_services.dart'; import 'ui_services.dart';
import '../view_model/pdf_view_model.dart';
class PdfSignatureHomePage extends ConsumerStatefulWidget { class PdfSignatureHomePage extends ConsumerStatefulWidget {
const PdfSignatureHomePage({super.key}); const PdfSignatureHomePage({super.key});
@ -80,26 +78,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
} }
void _jumpToPage(int page) { void _jumpToPage(int page) {
final controller = ref.read(pdfViewerControllerProvider); ref.read(documentRepositoryProvider.notifier).jumpTo(page);
final current = ref.read(currentPageProvider);
final pdf = ref.read(documentRepositoryProvider);
int target;
if (page == -1) {
target = (current - 1).clamp(1, pdf.pageCount);
} else {
target = page.clamp(1, pdf.pageCount);
}
// Update reactive page providers so UI/tests reflect navigation even if controller is a stub
if (current != target) {
ref.read(currentPageProvider.notifier).state = target;
// Also notify view model (if used elsewhere) via its public API
try {
ref.read(pdfViewModelProvider.notifier).jumpToPage(target);
} catch (_) {
// ignore if provider not available
}
}
if (controller.isReady) controller.goToPage(pageNumber: target);
} }
Future<Uint8List?> _loadSignatureFromFile() async { Future<Uint8List?> _loadSignatureFromFile() async {
@ -135,10 +114,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
enableDrag: false, enableDrag: false,
builder: builder: (_) => const DrawCanvas(),
(_) => DrawCanvas(
onConfirm: (bytes) => Navigator.of(context).pop(bytes),
),
); );
if (result != null && result.isNotEmpty) { if (result != null && result.isNotEmpty) {
// In simplified UI, adding to library isn't implemented // In simplified UI, adding to library isn't implemented
@ -296,16 +272,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Provide controller override so descendants can access it.
return ProviderScope(
overrides: [pdfViewerControllerProvider.overrideWithValue(_controller)],
child: _buildScaffold(context),
);
}
late final PdfViewerController _controller = PdfViewerController();
Widget _buildScaffold(BuildContext context) {
final isExporting = ref.watch(exportingProvider); final isExporting = ref.watch(exportingProvider);
final l = AppLocalizations.of(context); final l = AppLocalizations.of(context);
return Scaffold( return Scaffold(
@ -345,24 +311,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
_applySidebarVisibility(); _applySidebarVisibility();
}), }),
), ),
// Expose a compact signature drawer trigger area for tests when sidebar hidden
if (!_showSignaturesSidebar)
Align(
alignment: Alignment.centerLeft,
child: SizedBox(
height:
0, // zero-height container exposing buttons offstage
width: 0,
child: Offstage(
offstage: true,
child: SignaturesSidebar(
onLoadSignatureFromFile: _loadSignatureFromFile,
onOpenDrawCanvas: _openDrawCanvas,
onSave: _saveSignedPdf,
),
),
),
),
const SizedBox(height: 8), const SizedBox(height: 8),
Expanded( Expanded(
child: MultiSplitView( child: MultiSplitView(

View File

@ -4,7 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'pdf_providers.dart';
class PdfToolbar extends ConsumerStatefulWidget { class PdfToolbar extends ConsumerStatefulWidget {
const PdfToolbar({ const PdfToolbar({
@ -57,9 +56,8 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final pdf = ref.watch(documentRepositoryProvider); final pdf = ref.watch(documentRepositoryProvider);
final currentPage = ref.watch(currentPageProvider);
final l = AppLocalizations.of(context); final l = AppLocalizations.of(context);
final pageInfo = l.pageInfo(currentPage, pdf.pageCount); final pageInfo = l.pageInfo(pdf.currentPage, pdf.pageCount);
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
@ -105,7 +103,8 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
onPressed: onPressed:
widget.disabled widget.disabled
? null ? null
: () => widget.onJumpToPage(-1), : () =>
widget.onJumpToPage(pdf.currentPage - 1),
icon: const Icon(Icons.chevron_left), icon: const Icon(Icons.chevron_left),
tooltip: l.prev, tooltip: l.prev,
), ),
@ -116,7 +115,8 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
onPressed: onPressed:
widget.disabled widget.disabled
? null ? null
: () => widget.onJumpToPage(currentPage + 1), : () =>
widget.onJumpToPage(pdf.currentPage + 1),
icon: const Icon(Icons.chevron_right), icon: const Icon(Icons.chevron_right),
tooltip: l.next, tooltip: l.next,
), ),

View File

@ -4,9 +4,9 @@ import 'package:pdfrx/pdfrx.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import 'pdf_page_overlays.dart'; import 'pdf_page_overlays.dart';
import 'pdf_providers.dart';
import './pdf_mock_continuous_list.dart'; import './pdf_mock_continuous_list.dart';
import '../../signature/widgets/signature_drag_data.dart'; import '../../signature/widgets/signature_drag_data.dart';
import 'pdf_providers.dart';
class PdfViewerWidget extends ConsumerStatefulWidget { class PdfViewerWidget extends ConsumerStatefulWidget {
const PdfViewerWidget({ const PdfViewerWidget({
@ -38,9 +38,6 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
PdfViewerController? _controller; PdfViewerController? _controller;
PdfDocumentRef? _documentRef; PdfDocumentRef? _documentRef;
// Public getter for testing the actual viewer page
int? get viewerCurrentPage => _controller?.pageNumber;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -57,9 +54,6 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final document = ref.watch(documentRepositoryProvider); final document = ref.watch(documentRepositoryProvider);
final useMock = ref.watch(useMockViewerProvider); final useMock = ref.watch(useMockViewerProvider);
ref.watch(activeRectProvider); // trigger rebuild when active rect changes
// Watch to rebuild on page change
ref.watch(currentPageProvider);
// Update document ref when document changes // Update document ref when document changes
if (document.loaded && document.pickedPdfBytes != null) { if (document.loaded && document.pickedPdfBytes != null) {
@ -115,8 +109,9 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
.setPageCount(document.pages.length); .setPageCount(document.pages.length);
}, },
onPageChanged: (page) { onPageChanged: (page) {
// Update current page in repository
if (page != null) { if (page != null) {
ref.read(currentPageProvider.notifier).state = page; ref.read(documentRepositoryProvider.notifier).jumpTo(page);
} }
}, },
), ),
@ -130,7 +125,8 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
// For real PDF viewer, we need to calculate which page was dropped on // For real PDF viewer, we need to calculate which page was dropped on
// This is a simplified implementation - in a real app you'd need to // This is a simplified implementation - in a real app you'd need to
// determine the exact page and position within that page // determine the exact page and position within that page
final currentPage = ref.read(currentPageProvider); final currentPage =
ref.read(documentRepositoryProvider).currentPage;
// Create a default rect for the signature (can be adjusted later) // Create a default rect for the signature (can be adjusted later)
final rect = const Rect.fromLTWH(0.1, 0.1, 0.2, 0.1); final rect = const Rect.fromLTWH(0.1, 0.1, 0.2, 0.1);
@ -143,7 +139,6 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
rect: rect, rect: rect,
asset: dragData.card?.asset, asset: dragData.card?.asset,
rotationDeg: dragData.card?.rotationDeg ?? 0.0, rotationDeg: dragData.card?.rotationDeg ?? 0.0,
graphicAdjust: dragData.card?.graphicAdjust,
); );
}, },
builder: (context, candidateData, rejectedData) { builder: (context, candidateData, rejectedData) {
@ -168,7 +163,7 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
// to handle overlays for each page properly // to handle overlays for each page properly
return PdfPageOverlays( return PdfPageOverlays(
pageSize: widget.pageSize, pageSize: widget.pageSize,
pageNumber: ref.watch(currentPageProvider), pageNumber: document.currentPage,
onDragSignature: widget.onDragSignature, onDragSignature: widget.onDragSignature,
onResizeSignature: widget.onResizeSignature, onResizeSignature: widget.onResizeSignature,
onConfirmSignature: widget.onConfirmSignature, onConfirmSignature: widget.onConfirmSignature,

View File

@ -5,10 +5,8 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
// No direct model construction needed here // No direct model construction needed here
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'image_editor_dialog.dart'; import 'image_editor_dialog.dart';
import '../../signature/widgets/signature_card.dart'; import '../../signature/widgets/signature_card.dart';
import 'pdf_providers.dart';
/// Data for drag-and-drop is in signature_drag_data.dart /// Data for drag-and-drop is in signature_drag_data.dart
@ -34,7 +32,7 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = AppLocalizations.of(context); final l = AppLocalizations.of(context);
final library = ref.watch(signatureCardRepositoryProvider); final library = ref.watch(signatureAssetRepositoryProvider);
// Exporting flag lives in ui_services; keep drawer interactive regardless here. // Exporting flag lives in ui_services; keep drawer interactive regardless here.
final isExporting = false; final isExporting = false;
final disabled = widget.disabled || isExporting; final disabled = widget.disabled || isExporting;
@ -43,21 +41,20 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
if (library.isNotEmpty) ...[ if (library.isNotEmpty) ...[
for (final card in library) ...[ for (final a in library) ...[
Card( Card(
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: Padding( child: Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: SignatureCard( child: SignatureCard(
key: ValueKey('sig_card_${library.indexOf(card)}'), key: ValueKey('sig_card_${library.indexOf(a)}'),
asset: card.asset, asset: a,
rotationDeg: card.rotationDeg, rotationDeg: 0.0,
graphicAdjust: card.graphicAdjust,
disabled: disabled, disabled: disabled,
onDelete: onDelete:
() => ref () => ref
.read(signatureCardRepositoryProvider.notifier) .read(signatureAssetRepositoryProvider.notifier)
.remove(card), .remove(a),
onAdjust: () async { onAdjust: () async {
if (!mounted) return; if (!mounted) return;
await showDialog( await showDialog(
@ -65,11 +62,7 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
builder: (_) => const ImageEditorDialog(), builder: (_) => const ImageEditorDialog(),
); );
}, },
onTap: () { onTap: () {},
ref
.read(activeRectProvider.notifier)
.state = const Rect.fromLTWH(0.2, 0.2, 0.3, 0.15);
},
), ),
), ),
), ),

View File

@ -58,23 +58,13 @@ class _RotatedSignatureImageState extends State<RotatedSignatureImage> {
void _resolveImage() { void _resolveImage() {
_unlisten(); _unlisten();
// Decode synchronously to get aspect ratio // Decode synchronously to get aspect ratio
// Guard against empty / invalid bytes that some simplified tests may inject. final decoded = img.decodePng(widget.bytes);
if (widget.bytes.isEmpty) { if (decoded != null) {
_setAspectRatio(1.0); // assume square to avoid layout exceptions final w = decoded.width;
return; final h = decoded.height;
} if (w > 0 && h > 0) {
try { _setAspectRatio(w / h);
final decoded = img.decodePng(widget.bytes);
if (decoded != null) {
final w = decoded.width;
final h = decoded.height;
if (w > 0 && h > 0) {
_setAspectRatio(w / h);
}
} }
} catch (_) {
// Swallow decode errors for test-provided dummy data; assume square.
_setAspectRatio(1.0);
} }
final stream = _provider.resolve(createLocalImageConfiguration(context)); final stream = _provider.resolve(createLocalImageConfiguration(context));
_stream = stream; _stream = stream;

View File

@ -14,7 +14,6 @@ class SignatureCard extends StatelessWidget {
this.onAdjust, this.onAdjust,
this.useCurrentBytesForDrag = false, this.useCurrentBytesForDrag = false,
this.rotationDeg = 0.0, this.rotationDeg = 0.0,
this.graphicAdjust = const domain.GraphicAdjust(),
}); });
final domain.SignatureAsset asset; final domain.SignatureAsset asset;
final bool disabled; final bool disabled;
@ -23,7 +22,6 @@ class SignatureCard extends StatelessWidget {
final VoidCallback? onAdjust; final VoidCallback? onAdjust;
final bool useCurrentBytesForDrag; final bool useCurrentBytesForDrag;
final double rotationDeg; final double rotationDeg;
final domain.GraphicAdjust graphicAdjust;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -148,7 +146,6 @@ class SignatureCard extends StatelessWidget {
card: domain.SignatureCard( card: domain.SignatureCard(
asset: asset, asset: asset,
rotationDeg: rotationDeg, rotationDeg: rotationDeg,
graphicAdjust: graphicAdjust,
), ),
), ),
feedback: Opacity( feedback: Opacity(

View File

@ -1,61 +0,0 @@
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:pdf_signature/app.dart';
import 'package:pdf_signature/data/repositories/preferences_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
import 'package:pdf_signature/data/services/export_service.dart';
import 'package:pdf_signature/domain/models/model.dart';
class FakeExportService extends ExportService {
bool exported = false;
@override
Future<Uint8List?> exportSignedPdfFromBytes({
Map<String, Uint8List>? libraryBytes,
required Uint8List srcBytes,
required Size uiPageSize,
required Uint8List? signatureImageBytes,
Map<int, List<SignaturePlacement>>? placementsByPage,
double targetDpi = 144.0,
}) async => Uint8List.fromList([1, 2, 3]);
@override
Future<bool> saveBytesToFile({
required Uint8List bytes,
required String outputPath,
}) async {
exported = true;
return true;
}
}
Future<ProviderContainer> pumpApp(
WidgetTester tester, {
Map<String, Object> initialPrefs = const {},
}) async {
SharedPreferences.setMockInitialValues(initialPrefs);
final prefs = await SharedPreferences.getInstance();
final fakeExport = FakeExportService();
final container = ProviderContainer(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) => DocumentStateNotifier()..openSample(),
),
useMockViewerProvider.overrideWith((ref) => true),
exportServiceProvider.overrideWith((ref) => fakeExport),
savePathPickerProvider.overrideWith((ref) => () async => 'out.pdf'),
],
);
await tester.pumpWidget(
UncontrolledProviderScope(container: container, child: const MyApp()),
);
await tester.pumpAndSettle();
return container;
}

View File

@ -1,16 +1,11 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a document page is selected for signing /// Usage: a document page is selected for signing
Future<void> aDocumentPageIsSelectedForSigning(WidgetTester tester) async { Future<void> aDocumentPageIsSelectedForSigning(WidgetTester tester) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container; TestWorld.container = container;
// Ensure current page is 1 for consistent subsequent steps
try {
container.read(pdfViewModelProvider.notifier).jumpToPage(1);
} catch (_) {}
container.read(documentRepositoryProvider.notifier).jumpTo(1); container.read(documentRepositoryProvider.notifier).jumpTo(1);
} }

View File

@ -1,31 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../_test_helper.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a drawn signature exists in the canvas /// Usage: a drawn signature exists in the canvas
Future<void> aDrawnSignatureExistsInTheCanvas(WidgetTester tester) async { Future<void> aDrawnSignatureExistsInTheCanvas(WidgetTester tester) async {
// Tap the draw signature button to open the dialog final container = TestWorld.container ?? ProviderContainer();
if (find.byType(MaterialApp).evaluate().isEmpty) { final sigN = container.read(signatureProvider.notifier);
final container = await pumpApp(tester); sigN.setStrokes([
TestWorld.container = container; [const Offset(0, 0), const Offset(1, 1)],
} ]);
// Ensure button exists
expect(find.byKey(const Key('btn_drawer_draw_signature')), findsOneWidget);
await tester.tap(find.byKey(const Key('btn_drawer_draw_signature')));
await tester.pumpAndSettle();
// Now the DrawCanvas dialog should be open
expect(find.byKey(const Key('draw_canvas')), findsOneWidget);
// Simulate drawing strokes on the canvas
final canvas = find.byKey(const Key('hand_signature_pad'));
expect(canvas, findsOneWidget);
// Draw a simple stroke
await tester.drag(canvas, const Offset(50, 50));
await tester.drag(canvas, const Offset(100, 100));
await tester.drag(canvas, const Offset(150, 150));
// Do not confirm, so the canvas has strokes but is not closed
} }

View File

@ -4,8 +4,6 @@ import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a multi-page document is open /// Usage: a multi-page document is open
@ -21,11 +19,4 @@ Future<void> aMultipageDocumentIsOpen(WidgetTester tester) async {
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.openPicked(path: 'mock.pdf', pageCount: 5); .openPicked(path: 'mock.pdf', pageCount: 5);
// Reset page state providers
try {
container.read(currentPageProvider.notifier).state = 1;
} catch (_) {}
try {
container.read(pdfViewModelProvider.notifier).jumpToPage(1);
} catch (_) {}
} }

View File

@ -1,16 +1,37 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a signature asset is created /// Usage: a signature asset is created
Future<void> aSignatureAssetIsCreated(WidgetTester tester) async { Future<void> aSignatureAssetIsCreated(WidgetTester tester) async {
final container = TestWorld.container!; final container = TestWorld.container ?? ProviderContainer();
final assets = container.read(signatureAssetRepositoryProvider); TestWorld.container = container;
expect(assets, isNotEmpty);
// The last added should be the drawn one
final lastAsset = assets.last;
expect(lastAsset.name, 'drawing');
// Pump to ensure UI is updated // Ensure PDF is open
await tester.pump(); if (!container.read(documentRepositoryProvider).loaded) {
container
.read(documentRepositoryProvider.notifier)
.openPicked(path: 'mock.pdf', pageCount: 5);
}
// Create a dummy signature asset
final asset = SignatureAsset(bytes: Uint8List(100), name: 'Test Asset');
container
.read(signatureAssetRepositoryProvider.notifier)
.add(asset.bytes, name: asset.name);
// Place it on the current page
final pdf = container.read(documentRepositoryProvider);
container
.read(documentRepositoryProvider.notifier)
.addPlacement(
page: pdf.currentPage,
rect: Rect.fromLTWH(50, 50, 100, 50),
asset: asset,
);
} }

View File

@ -17,76 +17,7 @@ Future<void> aSignatureAssetIsLoadedOrDrawn(WidgetTester tester) async {
container.read(signatureCardRepositoryProvider.notifier).state = [ container.read(signatureCardRepositoryProvider.notifier).state = [
SignatureCard.initial(), SignatureCard.initial(),
]; ];
// Use a tiny valid PNG so any later image decoding succeeds. final bytes = Uint8List.fromList([1, 2, 3, 4, 5]);
final bytes = Uint8List.fromList([
0x89,
0x50,
0x4E,
0x47,
0x0D,
0x0A,
0x1A,
0x0A,
0x00,
0x00,
0x00,
0x0D,
0x49,
0x48,
0x44,
0x52,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x01,
0x08,
0x06,
0x00,
0x00,
0x00,
0x1F,
0x15,
0xC4,
0x89,
0x00,
0x00,
0x00,
0x0A,
0x49,
0x44,
0x41,
0x54,
0x78,
0x9C,
0x63,
0x60,
0x00,
0x00,
0x00,
0x02,
0x00,
0x01,
0xE5,
0x27,
0xD4,
0xA6,
0x00,
0x00,
0x00,
0x00,
0x49,
0x45,
0x4E,
0x44,
0xAE,
0x42,
0x60,
0x82,
]);
container container
.read(signatureAssetRepositoryProvider.notifier) .read(signatureAssetRepositoryProvider.notifier)
.add(bytes, name: 'test.png'); .add(bytes, name: 'test.png');

View File

@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a signature asset is placed on the page /// Usage: a signature asset is placed on the page
@ -36,12 +35,12 @@ Future<void> aSignatureAssetIsPlacedOnThePage(WidgetTester tester) async {
} }
// Place it on the current page // Place it on the current page
final currentPage = container.read(pdfViewModelProvider); final pdf = container.read(documentRepositoryProvider);
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.addPlacement( .addPlacement(
page: currentPage, page: pdf.currentPage,
rect: const Rect.fromLTWH(50, 50, 100, 50), rect: Rect.fromLTWH(50, 50, 100, 50),
asset: asset, asset: asset,
); );
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a signature placement appears on the page based on the signature card /// Usage: a signature placement appears on the page based on the signature card
@ -9,8 +8,7 @@ Future<void> aSignaturePlacementAppearsOnThePageBasedOnTheSignatureCard(
) async { ) async {
final container = TestWorld.container!; final container = TestWorld.container!;
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final page = container.read(pdfViewModelProvider); final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
final placements = pdf.placementsByPage[page] ?? const [];
expect( expect(
placements.isNotEmpty, placements.isNotEmpty,
true, true,

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';
@ -13,12 +12,12 @@ Future<void> aSignaturePlacementIsPlacedWithAPositionAndSizeRelativeToThePage(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container; TestWorld.container = container;
final currentPage = container.read(pdfViewModelProvider); final pdf = container.read(documentRepositoryProvider);
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.addPlacement( .addPlacement(
page: currentPage, page: pdf.currentPage,
rect: const Rect.fromLTWH(50, 50, 200, 100), rect: Rect.fromLTWH(50, 50, 200, 100),
asset: SignatureAsset(bytes: Uint8List(0), name: 'test.png'), asset: SignatureAsset(bytes: Uint8List(0), name: 'test.png'),
); );
} }

View File

@ -1,17 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../_test_helper.dart';
import '_world.dart';
/// Usage: an empty signature canvas /// Usage: an empty signature canvas
Future<void> anEmptySignatureCanvas(WidgetTester tester) async { Future<void> anEmptySignatureCanvas(WidgetTester tester) async {
// Pump the app so the signature drawer (and its draw button) exists. // Mock: assume canvas is empty
if (find.byType(MaterialApp).evaluate().isEmpty) {
final container = await pumpApp(tester);
TestWorld.container = container;
}
// The draw canvas should not be open initially
expect(find.byKey(const Key('draw_canvas')), findsNothing);
// Ensure the draw signature button is present
expect(find.byKey(const Key('btn_drawer_draw_signature')), findsOneWidget);
} }

View File

@ -1,7 +1,7 @@
import 'dart:ui';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: dragging or resizing one does not change the other /// Usage: dragging or resizing one does not change the other
@ -9,27 +9,26 @@ Future<void> draggingOrResizingOneDoesNotChangeTheOther(
WidgetTester tester, WidgetTester tester,
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final page = container.read(pdfViewModelProvider);
final list = container final list = container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.placementsOn(page); .placementsOn(1);
expect(list.length, greaterThanOrEqualTo(2)); expect(list.length, greaterThanOrEqualTo(2));
// Capture rects independently (avoid invalidation by mutation) final before = List<Rect>.from(list.take(2).map((p) => p.rect));
final firstRectBefore = list[0].rect; // Simulate changing the first only
final secondRectBefore = list[1].rect; final changed = before[0].inflate(5);
// Simulate modifying only the first placement's size
final changedFirst = firstRectBefore.inflate(5);
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.updatePlacementRect(page: page, index: 0, rect: changedFirst); .removePlacement(page: 1, index: 0);
container
.read(documentRepositoryProvider.notifier)
.addPlacement(
page: 1,
rect: changed,
asset: list[1].asset,
rotationDeg: list[1].rotationDeg,
);
final after = container final after = container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.placementsOn(page); .placementsOn(1);
expect(after.length, greaterThanOrEqualTo(2)); expect(after.any((p) => p.rect == before[1]), isTrue);
// First changed, second unchanged
expect(after[0].rect, isNot(equals(firstRectBefore)));
expect(after[0].rect, equals(changedFirst));
expect(after[1].rect, equals(secondRectBefore));
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: each signature placement can be dragged and resized independently /// Usage: each signature placement can be dragged and resized independently
@ -10,7 +9,6 @@ Future<void> eachSignaturePlacementCanBeDraggedAndResizedIndependently(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final page = container.read(pdfViewModelProvider); final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
final placements = pdf.placementsByPage[page] ?? const [];
expect(placements.length, greaterThan(1)); expect(placements.length, greaterThan(1));
} }

View File

@ -1,28 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../_test_helper.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '_world.dart'; import '_world.dart';
/// Usage: multiple strokes were drawn /// Usage: multiple strokes were drawn
Future<void> multipleStrokesWereDrawn(WidgetTester tester) async { Future<void> multipleStrokesWereDrawn(WidgetTester tester) async {
// Open the draw dialog final container = TestWorld.container ?? ProviderContainer();
if (find.byType(MaterialApp).evaluate().isEmpty) { container.read(signatureProvider.notifier).setStrokes([
final container = await pumpApp(tester); [const Offset(0, 0), const Offset(1, 1)],
TestWorld.container = container; [const Offset(2, 2), const Offset(3, 3)],
} ]);
expect(find.byKey(const Key('btn_drawer_draw_signature')), findsOneWidget);
await tester.tap(find.byKey(const Key('btn_drawer_draw_signature')));
await tester.pumpAndSettle();
// Draw multiple strokes
final canvas = find.byKey(const Key('hand_signature_pad'));
expect(canvas, findsOneWidget);
// First stroke
await tester.drag(canvas, const Offset(50, 50));
await tester.drag(canvas, const Offset(100, 100));
// Second stroke
await tester.drag(canvas, const Offset(200, 200));
await tester.drag(canvas, const Offset(250, 250));
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: only the selected signature placement is removed /// Usage: only the selected signature placement is removed
@ -10,7 +9,6 @@ Future<void> onlyTheSelectedSignaturePlacementIsRemoved(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final page = container.read(pdfViewModelProvider); final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
final placements = pdf.placementsByPage[page] ?? const [];
expect(placements.length, 2); // Started with 3, removed 1, should have 2 expect(placements.length, 2); // Started with 3, removed 1, should have 2
} }

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: page {5} becomes visible in the scroll area /// Usage: page {5} becomes visible in the scroll area
@ -10,5 +10,5 @@ Future<void> pageBecomesVisibleInTheScrollArea(
) async { ) async {
final page = param1.toInt(); final page = param1.toInt();
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
expect(c.read(pdfViewModelProvider), page); expect(c.read(documentRepositoryProvider).currentPage, page);
} }

View File

@ -1,18 +1,11 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: page {1} is displayed /// Usage: page {1} is displayed
Future<void> pageIsDisplayed(WidgetTester tester, num param1) async { Future<void> pageIsDisplayed(WidgetTester tester, num param1) async {
final expected = param1.toInt(); final expected = param1.toInt();
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
final vm = c.read(pdfViewModelProvider); expect(c.read(documentRepositoryProvider).currentPage, expected);
final legacy = c.read(currentPageProvider);
expect(
vm == expected || legacy == expected,
true,
reason: 'Expected page $expected but got vm=$vm current=$legacy',
);
} }

View File

@ -1,15 +1,14 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: resize to fit within bounding box /// Usage: resize to fit within bounding box
Future<void> resizeToFitWithinBoundingBox(WidgetTester tester) async { Future<void> resizeToFitWithinBoundingBox(WidgetTester tester) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final page = container.read(pdfViewModelProvider);
final placements = pdf.placementsByPage[page] ?? const []; final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
for (final placement in placements) { for (final placement in placements) {
// Assume page size is 800x600 for testing // Assume page size is 800x600 for testing
const pageWidth = 800.0; const pageWidth = 800.0;

View File

@ -1,28 +1,17 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import '_world.dart'; import '_world.dart';
import 'dart:ui';
/// Usage: signature placement occurs on the selected page /// Usage: signature placement occurs on the selected page
/// Simplified: directly adds a placement to page 1 if none exist yet.
Future<void> signaturePlacementOccursOnTheSelectedPage( Future<void> signaturePlacementOccursOnTheSelectedPage(
WidgetTester tester, WidgetTester tester,
) async { ) async {
final container = TestWorld.container!; final container = TestWorld.container ?? ProviderContainer();
final repo = container.read(documentRepositoryProvider.notifier); TestWorld.container = container;
final state = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final page = 1;
if ((state.placementsByPage[page] ?? const []).isEmpty) { // Check that there's at least one placement on the current page
final assets = container.read(signatureAssetRepositoryProvider); final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
final asset = assets.isNotEmpty ? assets.last : null; expect(placements.isNotEmpty, true);
repo.addPlacement(
page: page,
rect: const Rect.fromLTWH(0.1, 0.1, 0.2, 0.1),
asset: asset,
);
}
await tester.pump();
final updated = container.read(documentRepositoryProvider);
expect(updated.placementsByPage[page], isNotEmpty);
} }

View File

@ -6,17 +6,8 @@ Future<void> theAppLanguageIs(
WidgetTester tester, WidgetTester tester,
String languageWrapped, String languageWrapped,
) async { ) async {
String unwrap(String s) { String unwrap(String s) =>
var r = s.trim(); s.startsWith('{') && s.endsWith('}') ? s.substring(1, s.length - 1) : s;
if (r.startsWith('{') && r.endsWith('}')) {
r = r.substring(1, r.length - 1).trim();
}
if (r.startsWith("'") && r.endsWith("'")) {
r = r.substring(1, r.length - 1);
}
return r;
}
final lang = unwrap(languageWrapped); final lang = unwrap(languageWrapped);
expect(TestWorld.currentLanguage, lang); expect(TestWorld.currentLanguage, lang);
} }

View File

@ -1,100 +1,12 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:pdf_signature/app.dart';
import 'package:pdf_signature/data/repositories/preferences_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import '_world.dart'; import '_world.dart';
class _BridgedSignatureCardStateNotifier extends SignatureCardStateNotifier {
void setAll(List<SignatureCard> cards) {
state = List.unmodifiable(cards);
}
}
/// Usage: the app launches /// Usage: the app launches
Future<void> theAppLaunches(WidgetTester tester) async { Future<void> theAppLaunches(WidgetTester tester) async {
// Preserve any previously simulated stored preferences (used by scenarios // Read stored preferences and apply
// that set TestWorld.prefs BEFORE launching to emulate a prior run). final theme = TestWorld.prefs['theme'] ?? 'system';
final preservedPrefs = Map<String, String>.from(TestWorld.prefs); TestWorld.selectedTheme = theme;
TestWorld.reset(); TestWorld.currentTheme = theme == 'system' ? TestWorld.systemTheme : theme;
if (preservedPrefs.isNotEmpty) { final lang = TestWorld.prefs['language'] ?? TestWorld.deviceLocale;
TestWorld.prefs = preservedPrefs; // restore for this launch TestWorld.currentLanguage = lang;
}
SharedPreferences.setMockInitialValues(TestWorld.prefs);
final prefs = await SharedPreferences.getInstance();
final container = ProviderContainer(
overrides: [
preferencesRepositoryProvider.overrideWith(
(ref) => PreferencesStateNotifier(prefs),
),
documentRepositoryProvider.overrideWith(
(ref) => DocumentStateNotifier()..openSample(),
),
useMockViewerProvider.overrideWith((ref) => true),
// Bridge: automatically mirror assets into signature cards so legacy
// feature steps that expect SignatureCard widgets keep working even
// though the production UI currently only stores raw assets.
signatureCardRepositoryProvider.overrideWith((ref) {
final notifier = _BridgedSignatureCardStateNotifier();
ref.listen<List<SignatureAsset>>(signatureAssetRepositoryProvider, (
prev,
next,
) {
for (final asset in next) {
if (!notifier.state.any((c) => identical(c.asset, asset))) {
notifier.add(SignatureCard(asset: asset, rotationDeg: 0.0));
}
}
// Remove cards whose assets were removed
final remaining =
notifier.state.where((c) => next.contains(c.asset)).toList();
if (remaining.length != notifier.state.length) {
notifier.setAll(remaining);
}
});
return notifier;
}),
],
);
TestWorld.container = container;
await tester.pumpWidget(
UncontrolledProviderScope(container: container, child: const MyApp()),
);
await tester.pumpAndSettle();
// ----- Simulated app preference initialization logic -----
// Theme initialization & validation
const validThemes = {'light', 'dark', 'system'};
final storedTheme = TestWorld.prefs['theme'];
if (storedTheme != null && validThemes.contains(storedTheme)) {
TestWorld.selectedTheme = storedTheme;
} else {
// Fallback to system if missing/invalid
TestWorld.selectedTheme = 'system';
TestWorld.prefs['theme'] = 'system';
}
// currentTheme reflects either explicit theme or current system appearance
TestWorld.currentTheme =
TestWorld.selectedTheme == 'system'
? TestWorld.systemTheme
: TestWorld.selectedTheme;
// Language initialization & validation
const validLangs = {'en', 'zh-TW', 'es'};
final storedLang = TestWorld.prefs['language'];
if (storedLang != null && validLangs.contains(storedLang)) {
TestWorld.currentLanguage = storedLang;
} else {
// Fallback to device locale
TestWorld.currentLanguage = TestWorld.deviceLocale;
TestWorld.prefs['language'] = TestWorld.deviceLocale;
}
} }

View File

@ -3,17 +3,8 @@ import '_world.dart';
/// Usage: the app UI theme is {"<theme>"} /// Usage: the app UI theme is {"<theme>"}
Future<void> theAppUiThemeIs(WidgetTester tester, String themeWrapped) async { Future<void> theAppUiThemeIs(WidgetTester tester, String themeWrapped) async {
String unwrap(String s) { String unwrap(String s) =>
var r = s.trim(); s.startsWith('{') && s.endsWith('}') ? s.substring(1, s.length - 1) : s;
if (r.startsWith('{') && r.endsWith('}')) {
r = r.substring(1, r.length - 1).trim();
}
if (r.startsWith("'") && r.endsWith("'")) {
r = r.substring(1, r.length - 1);
}
return r;
}
final t = unwrap(themeWrapped); final t = unwrap(themeWrapped);
if (t == 'system') { if (t == 'system') {
// When checking for 'system', we validate that selectedTheme is system // When checking for 'system', we validate that selectedTheme is system

View File

@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
/// Usage: the canvas becomes blank /// Usage: the canvas becomes blank
Future<void> theCanvasBecomesBlank(WidgetTester tester) async { Future<void> theCanvasBecomesBlank(WidgetTester tester) async {
// The canvas should still be open // Mock: assume canvas is blank
expect(find.byKey(const Key('draw_canvas')), findsOneWidget); expect(true, isTrue);
// Assume it's blank after clear
} }

View File

@ -1,11 +1,11 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the first page is displayed /// Usage: the first page is displayed
Future<void> theFirstPageIsDisplayed(WidgetTester tester) async { Future<void> theFirstPageIsDisplayed(WidgetTester tester) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final currentPage = container.read(pdfViewModelProvider); final pdf = container.read(documentRepositoryProvider);
expect(currentPage, 1); expect(pdf.currentPage, 1);
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the Go to input cannot be used /// Usage: the Go to input cannot be used
@ -9,9 +8,8 @@ Future<void> theGoToInputCannotBeUsed(WidgetTester tester) async {
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
// Not loaded, currentPage should remain 1 even after jump attempt // Not loaded, currentPage should remain 1 even after jump attempt
expect(c.read(documentRepositoryProvider).loaded, isFalse); expect(c.read(documentRepositoryProvider).loaded, isFalse);
final before = c.read(pdfViewModelProvider); final before = c.read(documentRepositoryProvider).currentPage;
// documentRepository jumpTo no longer changes page; ensure unchanged
c.read(documentRepositoryProvider.notifier).jumpTo(3); c.read(documentRepositoryProvider.notifier).jumpTo(3);
final after = c.read(pdfViewModelProvider); final after = c.read(documentRepositoryProvider).currentPage;
expect(before, equals(after)); expect(before, equals(after));
} }

View File

@ -1,8 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the last page is displayed (page {5}) /// Usage: the last page is displayed (page {5})
@ -11,11 +9,5 @@ Future<void> theLastPageIsDisplayedPage(WidgetTester tester, num param1) async {
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
final pdf = c.read(documentRepositoryProvider); final pdf = c.read(documentRepositoryProvider);
expect(pdf.pageCount, last); expect(pdf.pageCount, last);
final vm = c.read(pdfViewModelProvider); expect(pdf.currentPage, last);
final legacy = c.read(currentPageProvider);
expect(
vm == last || legacy == last,
true,
reason: 'Expected last page $last but got vm=$vm current=$legacy',
);
} }

View File

@ -1,9 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '_world.dart';
/// Usage: the last stroke is removed /// Usage: the last stroke is removed
Future<void> theLastStrokeIsRemoved(WidgetTester tester) async { Future<void> theLastStrokeIsRemoved(WidgetTester tester) async {
// The canvas should still be open final container = TestWorld.container ?? ProviderContainer();
expect(find.byKey(const Key('draw_canvas')), findsOneWidget); final sig = container.read(signatureProvider);
// Assume the last stroke is removed expect(sig.strokes.length, 1);
} }

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the left pages overview highlights page {5} /// Usage: the left pages overview highlights page {5}
@ -10,5 +10,5 @@ Future<void> theLeftPagesOverviewHighlightsPage(
) async { ) async {
final n = param1.toInt(); final n = param1.toInt();
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
expect(c.read(pdfViewModelProvider), n); expect(c.read(documentRepositoryProvider).currentPage, n);
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the other signature placements remain unchanged /// Usage: the other signature placements remain unchanged
@ -9,7 +8,6 @@ Future<void> theOtherSignaturePlacementsRemainUnchanged(
) async { ) async {
final container = TestWorld.container!; final container = TestWorld.container!;
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final page = container.read(pdfViewModelProvider); final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
final placements = pdf.placementsByPage[page] ?? const [];
expect(placements.length, 2); // Should have 2 remaining after deleting 1 expect(placements.length, 2); // Should have 2 remaining after deleting 1
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the page label shows "Page {5} of {5}" /// Usage: the page label shows "Page {5} of {5}"
@ -14,6 +13,6 @@ Future<void> thePageLabelShowsPageOf(
final total = param2.toInt(); final total = param2.toInt();
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
final pdf = c.read(documentRepositoryProvider); final pdf = c.read(documentRepositoryProvider);
expect(c.read(pdfViewModelProvider), current); expect(pdf.currentPage, current);
expect(pdf.pageCount, total); expect(pdf.pageCount, total);
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the signature placement remains within the page area /// Usage: the signature placement remains within the page area
@ -10,8 +9,8 @@ Future<void> theSignaturePlacementRemainsWithinThePageArea(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final page = container.read(pdfViewModelProvider);
final placements = pdf.placementsByPage[page] ?? const []; final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
for (final placement in placements) { for (final placement in placements) {
// Assume page size is 800x600 for testing // Assume page size is 800x600 for testing
const pageWidth = 800.0; const pageWidth = 800.0;

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the signature placement rotates around its center in real time /// Usage: the signature placement rotates around its center in real time
@ -10,9 +9,8 @@ Future<void> theSignaturePlacementRotatesAroundItsCenterInRealTime(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final currentPage = container.read(pdfViewModelProvider);
final placements = pdf.placementsByPage[currentPage] ?? []; final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
if (placements.isNotEmpty) { if (placements.isNotEmpty) {
final placement = placements[0]; final placement = placements[0];
expect(placement.rotationDeg, 45.0); expect(placement.rotationDeg, 45.0);

View File

@ -1,15 +1,14 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the size and position update in real time /// Usage: the size and position update in real time
Future<void> theSizeAndPositionUpdateInRealTime(WidgetTester tester) async { Future<void> theSizeAndPositionUpdateInRealTime(WidgetTester tester) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
final page = container.read(pdfViewModelProvider);
final placements = pdf.placementsByPage[page] ?? const []; final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
if (placements.isNotEmpty) { if (placements.isNotEmpty) {
final currentRect = placements[0].rect; final currentRect = placements[0].rect;
expect(currentRect.center, isNot(TestWorld.prevCenter)); expect(currentRect.center, isNot(TestWorld.prevCenter));

View File

@ -1,15 +1,16 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user can move to the next or previous page /// Usage: the user can move to the next or previous page
Future<void> theUserCanMoveToTheNextOrPreviousPage(WidgetTester tester) async { Future<void> theUserCanMoveToTheNextOrPreviousPage(WidgetTester tester) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final vm = container.read(pdfViewModelProvider.notifier); final pdfN = container.read(documentRepositoryProvider.notifier);
expect(container.read(pdfViewModelProvider), 1); final pdf = container.read(documentRepositoryProvider);
vm.jumpToPage(2); expect(pdf.currentPage, 1);
expect(container.read(pdfViewModelProvider), 2); pdfN.jumpTo(2);
vm.jumpToPage(1); expect(container.read(documentRepositoryProvider).currentPage, 2);
expect(container.read(pdfViewModelProvider), 1); pdfN.jumpTo(1);
expect(container.read(documentRepositoryProvider).currentPage, 1);
} }

View File

@ -1,9 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '_world.dart';
/// Usage: the user chooses undo /// Usage: the user chooses undo
Future<void> theUserChoosesUndo(WidgetTester tester) async { Future<void> theUserChoosesUndo(WidgetTester tester) async {
// Tap the undo button final container = TestWorld.container ?? ProviderContainer();
await tester.tap(find.byKey(const Key('btn_canvas_undo'))); final sig = container.read(signatureProvider);
await tester.pumpAndSettle(); if (sig.strokes.isNotEmpty) {
final newStrokes = List<List<Offset>>.from(sig.strokes)..removeLast();
container.read(signatureProvider.notifier).setStrokes(newStrokes);
}
} }

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '_world.dart';
/// Usage: the user clears the canvas /// Usage: the user clears the canvas
Future<void> theUserClearsTheCanvas(WidgetTester tester) async { Future<void> theUserClearsTheCanvas(WidgetTester tester) async {
// Tap the clear button final container = TestWorld.container ?? ProviderContainer();
await tester.tap(find.byKey(const Key('btn_canvas_clear'))); container.read(signatureProvider.notifier).setStrokes([]);
await tester.pumpAndSettle();
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user clicks the Go to apply button /// Usage: the user clicks the Go to apply button
@ -9,12 +8,7 @@ Future<void> theUserClicksTheGoToApplyButton(WidgetTester tester) async {
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
final pending = TestWorld.pendingGoTo; final pending = TestWorld.pendingGoTo;
if (pending != null) { if (pending != null) {
try { c.read(documentRepositoryProvider.notifier).jumpTo(pending);
c.read(currentPageProvider.notifier).state = pending;
} catch (_) {}
try {
c.read(pdfViewModelProvider.notifier).jumpToPage(pending);
} catch (_) {}
await tester.pump(); await tester.pump();
} }
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user clicks the thumbnail for page {2} /// Usage: the user clicks the thumbnail for page {2}
@ -11,11 +10,6 @@ Future<void> theUserClicksTheThumbnailForPage(
) async { ) async {
final page = param1.toInt(); final page = param1.toInt();
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
try { c.read(documentRepositoryProvider.notifier).jumpTo(page);
c.read(currentPageProvider.notifier).state = page;
} catch (_) {}
try {
c.read(pdfViewModelProvider.notifier).jumpToPage(page);
} catch (_) {}
await tester.pump(); await tester.pump();
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user deletes one selected signature placement /// Usage: the user deletes one selected signature placement
@ -10,13 +9,13 @@ Future<void> theUserDeletesOneSelectedSignaturePlacement(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container; TestWorld.container = container;
final currentPage = container.read(pdfViewModelProvider); final pdf = container.read(documentRepositoryProvider);
final placements = container final placements = container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.placementsOn(currentPage); .placementsOn(pdf.currentPage);
if (placements.isNotEmpty) { if (placements.isNotEmpty) {
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.removePlacement(page: currentPage, index: 0); .removePlacement(page: pdf.currentPage, index: 0);
} }
} }

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user drags handles to resize and drags to reposition /// Usage: the user drags handles to resize and drags to reposition
@ -11,10 +10,10 @@ Future<void> theUserDragsHandlesToResizeAndDragsToReposition(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container; TestWorld.container = container;
final pdf = container.read(documentRepositoryProvider);
final pdfN = container.read(documentRepositoryProvider.notifier); final pdfN = container.read(documentRepositoryProvider.notifier);
final currentPage = container.read(pdfViewModelProvider);
final placements = pdfN.placementsOn(currentPage); final placements = pdfN.placementsOn(pdf.currentPage);
if (placements.isNotEmpty) { if (placements.isNotEmpty) {
final currentRect = placements[0].rect; final currentRect = placements[0].rect;
TestWorld.prevCenter = currentRect.center; TestWorld.prevCenter = currentRect.center;
@ -26,6 +25,6 @@ Future<void> theUserDragsHandlesToResizeAndDragsToReposition(
height: currentRect.height + 30, height: currentRect.height + 30,
); );
pdfN.updatePlacementRect(page: currentPage, index: 0, rect: newRect); pdfN.updatePlacementRect(page: pdf.currentPage, index: 0, rect: newRect);
} }
} }

View File

@ -6,7 +6,6 @@ import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user drags this signature card on the page of the document to place a signature placement /// Usage: the user drags this signature card on the page of the document to place a signature placement
@ -48,14 +47,13 @@ theUserDragsThisSignatureCardOnThePageOfTheDocumentToPlaceASignaturePlacement(
final drop_card = temp_card; final drop_card = temp_card;
// Place it on the current page // Place it on the current page
final currentPage = container.read(pdfViewModelProvider); final pdf = container.read(documentRepositoryProvider);
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.addPlacement( .addPlacement(
page: currentPage, page: pdf.currentPage,
rect: const Rect.fromLTWH(100, 100, 100, 50), rect: Rect.fromLTWH(100, 100, 100, 50),
asset: drop_card.asset, asset: drop_card.asset,
rotationDeg: drop_card.rotationDeg, rotationDeg: drop_card.rotationDeg,
graphicAdjust: drop_card.graphicAdjust,
); );
} }

View File

@ -1,124 +1,13 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '_world.dart'; import '_world.dart';
import '../_test_helper.dart';
/// Usage: the user draws strokes and confirms /// Usage: the user draws strokes and confirms
Future<void> theUserDrawsStrokesAndConfirms(WidgetTester tester) async { Future<void> theUserDrawsStrokesAndConfirms(WidgetTester tester) async {
// Ensure app is pumped if not already final container = TestWorld.container ?? ProviderContainer();
if (find.byType(MaterialApp).evaluate().isEmpty) { TestWorld.container = container;
final container = await pumpApp(tester); // Simulate drawn signature bytes
TestWorld.container = container; final bytes = Uint8List.fromList([1, 2, 3]);
} container.read(signatureProvider.notifier).setImageBytes(bytes);
// If the drawer button isn't in the tree (simplified UI), inject a hidden button that opens the canvas
// App provides the button via signature sidebar; no injection needed now
// Tap the draw signature button to open the dialog
await tester.tap(find.byKey(const Key('btn_drawer_draw_signature')));
await tester.pumpAndSettle();
// Now the DrawCanvas dialog should be open
expect(find.byKey(const Key('draw_canvas')), findsOneWidget);
// Simulate drawing strokes on the canvas
final canvas = find.byKey(const Key('hand_signature_pad'));
expect(canvas, findsOneWidget);
// Draw a simple stroke
await tester.drag(canvas, const Offset(50, 50));
await tester.drag(canvas, const Offset(100, 100));
await tester.drag(canvas, const Offset(150, 150));
// Check confirm button is there
expect(find.byKey(const Key('btn_canvas_confirm')), findsOneWidget);
// Tap confirm
await tester.tap(find.byKey(const Key('btn_canvas_confirm')));
await tester.pumpAndSettle();
// Dialog should be closed
expect(find.byKey(const Key('draw_canvas')), findsNothing);
// Inject a dummy asset into repository (app does not auto-add drawn bytes yet)
final container = TestWorld.container;
if (container != null) {
container
.read(signatureAssetRepositoryProvider.notifier)
.add(
// Tiny 1x1 transparent PNG (duplicated constant for test clarity)
Uint8List.fromList([
0x89,
0x50,
0x4E,
0x47,
0x0D,
0x0A,
0x1A,
0x0A,
0x00,
0x00,
0x00,
0x0D,
0x49,
0x48,
0x44,
0x52,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x01,
0x08,
0x06,
0x00,
0x00,
0x00,
0x1F,
0x15,
0xC4,
0x89,
0x00,
0x00,
0x00,
0x0A,
0x49,
0x44,
0x41,
0x54,
0x78,
0x9C,
0x63,
0x60,
0x00,
0x00,
0x00,
0x02,
0x00,
0x01,
0xE5,
0x27,
0xD4,
0xA6,
0x00,
0x00,
0x00,
0x00,
0x49,
0x45,
0x4E,
0x44,
0xAE,
0x42,
0x60,
0x82,
]),
name: 'drawing',
);
}
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user enters {99} into the Go to input and applies it /// Usage: the user enters {99} into the Go to input and applies it
@ -11,14 +10,6 @@ Future<void> theUserEntersIntoTheGoToInputAndAppliesIt(
) async { ) async {
final value = param1.toInt(); final value = param1.toInt();
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
// Clamp value to valid range (1..pageCount) mimicking UI behavior c.read(documentRepositoryProvider.notifier).jumpTo(value);
final clamped =
value < 1 ? 1 : value; // upper bound validated in last-page check step
try {
c.read(currentPageProvider.notifier).state = clamped;
} catch (_) {}
try {
c.read(pdfViewModelProvider.notifier).jumpToPage(clamped);
} catch (_) {}
await tester.pump(); await tester.pump();
} }

View File

@ -1,18 +1,12 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user jumps to page {2} /// Usage: the user jumps to page {2}
Future<void> theUserJumpsToPage(WidgetTester tester, num param1) async { Future<void> theUserJumpsToPage(WidgetTester tester, num param1) async {
final page = param1.toInt(); final page = param1.toInt();
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
try { c.read(documentRepositoryProvider.notifier).jumpTo(page);
c.read(currentPageProvider.notifier).state = page;
} catch (_) {}
try {
c.read(pdfViewModelProvider.notifier).jumpToPage(page);
} catch (_) {}
await tester.pump(); await tester.pump();
} }

View File

@ -4,8 +4,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user navigates to page {5} and places another signature placement /// Usage: the user navigates to page {5} and places another signature placement
@ -16,13 +14,7 @@ Future<void> theUserNavigatesToPageAndPlacesAnotherSignaturePlacement(
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container; TestWorld.container = container;
final page = param1.toInt(); final page = param1.toInt();
// Update page providers directly (repository jumpTo is a no-op now) container.read(documentRepositoryProvider.notifier).jumpTo(page);
try {
container.read(currentPageProvider.notifier).state = page;
} catch (_) {}
try {
container.read(pdfViewModelProvider.notifier).jumpToPage(page);
} catch (_) {}
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.addPlacement( .addPlacement(

View File

@ -4,7 +4,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user places two signature placements on the same page /// Usage: the user places two signature placements on the same page
@ -13,45 +12,20 @@ Future<void> theUserPlacesTwoSignaturePlacementsOnTheSamePage(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container; TestWorld.container = container;
// pdfViewModelProvider returns 1-based current page final pdf = container.read(documentRepositoryProvider);
final page = container.read(pdfViewModelProvider); final page = pdf.currentPage;
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.addPlacement( .addPlacement(
page: page, page: page,
rect: Rect.fromLTWH(10, 10, 100, 50), rect: Rect.fromLTWH(10, 10, 100, 50),
asset: SignatureAsset( asset: SignatureAsset(bytes: Uint8List(0), name: 'sig1.png'),
bytes: Uint8List.fromList([
0x89,
0x50,
0x4E,
0x47,
0x0D,
0x0A,
0x1A,
0x0A,
]),
name: 'sig1.png',
),
); );
container container
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.addPlacement( .addPlacement(
page: page, page: page,
rect: Rect.fromLTWH(120, 10, 100, 50), rect: Rect.fromLTWH(120, 10, 100, 50),
asset: SignatureAsset( asset: SignatureAsset(bytes: Uint8List(0), name: 'sig2.png'),
bytes: Uint8List.fromList([
0x89,
0x50,
0x4E,
0x47,
0x0D,
0x0A,
0x1A,
0x0A,
0x00,
]),
name: 'sig2.png',
),
); );
} }

View File

@ -7,17 +7,8 @@ Future<void> theUserPreviouslySetThemeAndLanguage(
String themeWrapped, String themeWrapped,
String languageWrapped, String languageWrapped,
) async { ) async {
String unwrap(String s) { String unwrap(String s) =>
var r = s.trim(); s.startsWith('{') && s.endsWith('}') ? s.substring(1, s.length - 1) : s;
if (r.startsWith('{') && r.endsWith('}')) {
r = r.substring(1, r.length - 1).trim();
}
if (r.startsWith("'") && r.endsWith("'")) {
r = r.substring(1, r.length - 1);
}
return r;
}
final t = unwrap(themeWrapped); final t = unwrap(themeWrapped);
final lang = unwrap(languageWrapped); final lang = unwrap(languageWrapped);
// Simulate stored values // Simulate stored values

View File

@ -1,7 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user types {3} into the Go to input and presses Enter /// Usage: the user types {3} into the Go to input and presses Enter
@ -12,11 +11,6 @@ Future<void> theUserTypesIntoTheGoToInputAndPressesEnter(
final target = param1.toInt(); final target = param1.toInt();
final c = TestWorld.container ?? ProviderContainer(); final c = TestWorld.container ?? ProviderContainer();
TestWorld.container = c; TestWorld.container = c;
try { c.read(documentRepositoryProvider.notifier).jumpTo(target);
c.read(currentPageProvider.notifier).state = target;
} catch (_) {}
try {
c.read(pdfViewModelProvider.notifier).jumpToPage(target);
} catch (_) {}
await tester.pump(); await tester.pump();
} }

View File

@ -1,18 +1,19 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user uses rotate controls /// Usage: the user uses rotate controls
Future<void> theUserUsesRotateControls(WidgetTester tester) async { Future<void> theUserUsesRotateControls(WidgetTester tester) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
final pdf = container.read(documentRepositoryProvider);
final pdfN = container.read(documentRepositoryProvider.notifier); final pdfN = container.read(documentRepositoryProvider.notifier);
final currentPage = container.read(pdfViewModelProvider);
final placements = pdfN.placementsOn(currentPage); final placements = pdfN.placementsOn(pdf.currentPage);
if (placements.isNotEmpty) { if (placements.isNotEmpty) {
// Rotate the first placement by 45 degrees
pdfN.updatePlacementRotation( pdfN.updatePlacementRotation(
page: currentPage, page: pdf.currentPage,
index: 0, index: 0,
rotationDeg: 45.0, rotationDeg: 45.0,
); );

View File

@ -6,7 +6,6 @@ import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import '_world.dart'; import '_world.dart';
/// Usage: three signature placements are placed on the current page /// Usage: three signature placements are placed on the current page
@ -15,7 +14,6 @@ Future<void> threeSignaturePlacementsArePlacedOnTheCurrentPage(
) async { ) async {
final container = TestWorld.container ?? ProviderContainer(); final container = TestWorld.container ?? ProviderContainer();
TestWorld.container = container; TestWorld.container = container;
// Reset repositories to a known initial state
container.read(signatureAssetRepositoryProvider.notifier).state = []; container.read(signatureAssetRepositoryProvider.notifier).state = [];
container.read(documentRepositoryProvider.notifier).state = container.read(documentRepositoryProvider.notifier).state =
Document.initial(); Document.initial();
@ -26,7 +24,8 @@ Future<void> threeSignaturePlacementsArePlacedOnTheCurrentPage(
.read(documentRepositoryProvider.notifier) .read(documentRepositoryProvider.notifier)
.openPicked(path: 'mock.pdf', pageCount: 5); .openPicked(path: 'mock.pdf', pageCount: 5);
final pdfN = container.read(documentRepositoryProvider.notifier); final pdfN = container.read(documentRepositoryProvider.notifier);
final page = container.read(pdfViewModelProvider); final pdf = container.read(documentRepositoryProvider);
final page = pdf.currentPage;
pdfN.addPlacement( pdfN.addPlacement(
page: page, page: page,
rect: Rect.fromLTWH(10, 10, 50, 50), rect: Rect.fromLTWH(10, 10, 50, 50),

View File

@ -9,7 +9,6 @@ import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/ui_services.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/signature_asset.dart'; import 'package:pdf_signature/domain/models/signature_asset.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
@ -378,15 +377,6 @@ Future<void> pumpWithOpenPdfAndSig(WidgetTester tester) async {
repo.add(Uint8List.fromList(bytes), name: 'test'); repo.add(Uint8List.fromList(bytes), name: 'test');
return repo; return repo;
}), }),
signatureCardRepositoryProvider.overrideWith((ref) {
final cardRepo = SignatureCardStateNotifier();
final asset = SignatureAsset(
bytes: Uint8List.fromList(bytes),
name: 'test',
);
cardRepo.addWithAsset(asset, 0.0);
return cardRepo;
}),
// In new model, interactive overlay not implemented; keep library empty // In new model, interactive overlay not implemented; keep library empty
useMockViewerProvider.overrideWithValue(true), useMockViewerProvider.overrideWithValue(true),
exportingProvider.overrideWith((ref) => false), exportingProvider.overrideWith((ref) => false),

View File

@ -11,11 +11,11 @@ void main() {
expect((tester.widget<Text>(pageInfo)).data, 'Page 1/5'); expect((tester.widget<Text>(pageInfo)).data, 'Page 1/5');
await tester.tap(find.byKey(const Key('btn_next'))); await tester.tap(find.byKey(const Key('btn_next')));
await tester.pumpAndSettle(); await tester.pump();
expect((tester.widget<Text>(pageInfo)).data, 'Page 2/5'); expect((tester.widget<Text>(pageInfo)).data, 'Page 2/5');
await tester.tap(find.byKey(const Key('btn_prev'))); await tester.tap(find.byKey(const Key('btn_prev')));
await tester.pumpAndSettle(); await tester.pump();
expect((tester.widget<Text>(pageInfo)).data, 'Page 1/5'); expect((tester.widget<Text>(pageInfo)).data, 'Page 1/5');
}); });
@ -25,7 +25,7 @@ void main() {
final goto = find.byKey(const Key('txt_goto')); final goto = find.byKey(const Key('txt_goto'));
await tester.enterText(goto, '4'); await tester.enterText(goto, '4');
await tester.testTextInput.receiveAction(TextInputAction.done); await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle(); await tester.pump();
final pageInfo = find.byKey(const Key('lbl_page_info')); final pageInfo = find.byKey(const Key('lbl_page_info'));
expect((tester.widget<Text>(pageInfo)).data, 'Page 4/5'); expect((tester.widget<Text>(pageInfo)).data, 'Page 4/5');
}); });

View File

@ -12,7 +12,11 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
class _TestPdfController extends DocumentStateNotifier { class _TestPdfController extends DocumentStateNotifier {
_TestPdfController() : super() { _TestPdfController() : super() {
// Start with a loaded multi-page doc, page 1 of 5 // Start with a loaded multi-page doc, page 1 of 5
state = Document.initial().copyWith(loaded: true, pageCount: 5); state = Document.initial().copyWith(
loaded: true,
pageCount: 5,
currentPage: 1,
);
} }
} }

View File

@ -5,14 +5,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
class _TestPdfController extends DocumentStateNotifier { class _TestPdfController extends DocumentStateNotifier {
_TestPdfController() : super() { _TestPdfController() : super() {
state = Document.initial().copyWith(loaded: true, pageCount: 6); state = Document.initial().copyWith(
loaded: true,
pageCount: 6,
currentPage: 1,
);
} }
} }
@ -52,11 +55,8 @@ void main() {
), ),
); );
// Jump to page 5 right away via view model // Jump to page 5 right away
final ctx = tester.element(find.byType(PdfPageArea)); ctrl.jumpTo(5);
final container = ProviderScope.containerOf(ctx, listen: false);
final vm = container.read(pdfViewModelProvider.notifier);
vm.jumpToPage(5);
await tester.pump(); await tester.pump();
await tester.pumpAndSettle(const Duration(milliseconds: 600)); await tester.pumpAndSettle(const Duration(milliseconds: 600));

View File

@ -5,14 +5,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart'; import 'package:pdf_signature/ui/features/pdf/widgets/pdf_providers.dart';
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
class _TestPdfController extends DocumentStateNotifier { class _TestPdfController extends DocumentStateNotifier {
_TestPdfController() : super() { _TestPdfController() : super() {
state = Document.initial().copyWith(loaded: true, pageCount: 6); state = Document.initial().copyWith(
loaded: true,
pageCount: 6,
currentPage: 2,
);
} }
} }
@ -63,13 +66,9 @@ void main() {
double lastPixels = double lastPixels =
tester.state<ScrollableState>(scrollableFinder).position.pixels; tester.state<ScrollableState>(scrollableFinder).position.pixels;
final ctx = tester.element(find.byType(PdfPageArea));
final container = ProviderScope.containerOf(ctx, listen: false);
final vm = container.read(pdfViewModelProvider.notifier);
Future<void> jumpAndVerify(int targetPage) async { Future<void> jumpAndVerify(int targetPage) async {
final before = lastPixels; final before = lastPixels;
vm.jumpToPage(targetPage); ctrl.jumpTo(targetPage);
await tester.pump(); await tester.pump();
await tester.pumpAndSettle(const Duration(milliseconds: 600)); await tester.pumpAndSettle(const Duration(milliseconds: 600));
@ -93,7 +92,6 @@ void main() {
} }
// Jump to 4 different pages and verify each // Jump to 4 different pages and verify each
await jumpAndVerify(2);
await jumpAndVerify(5); await jumpAndVerify(5);
await jumpAndVerify(1); await jumpAndVerify(1);
await jumpAndVerify(6); await jumpAndVerify(6);

View File

@ -13,7 +13,11 @@ import 'package:pdf_signature/domain/models/model.dart';
class _TestPdfController extends DocumentStateNotifier { class _TestPdfController extends DocumentStateNotifier {
_TestPdfController() : super() { _TestPdfController() : super() {
state = Document.initial().copyWith(loaded: true, pageCount: 6); state = Document.initial().copyWith(
loaded: true,
pageCount: 6,
currentPage: 1,
);
} }
} }