From 473b957587584a473b9a811ed5bf4576c83925ca Mon Sep 17 00:00:00 2001 From: insleker Date: Mon, 15 Dec 2025 13:33:02 +0800 Subject: [PATCH] test: resolve error for integration test after upgrade to riverpod3 --- integration_test/export_flow_test.dart | 153 +++++++------- integration_test/pdf_view_test.dart | 198 ++++++++---------- .../repositories/document_repository.dart | 22 +- .../repositories/preferences_repository.dart | 4 + .../pdf/view_model/pdf_export_view_model.dart | 18 +- 5 files changed, 200 insertions(+), 195 deletions(-) diff --git a/integration_test/export_flow_test.dart b/integration_test/export_flow_test.dart index ce72bb3..6182e40 100644 --- a/integration_test/export_flow_test.dart +++ b/integration_test/export_flow_test.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/material.dart'; @@ -8,6 +9,7 @@ import 'package:integration_test/integration_test.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/preferences_repository.dart'; import 'package:pdf_signature/data/services/export_service.dart'; +import 'package:pdf_signature/domain/models/document.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/ui/features/pdf/view_model/pdf_export_view_model.dart'; import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.dart'; @@ -18,6 +20,23 @@ import 'package:shared_preferences/shared_preferences.dart'; // Note: We use the real ExportService via the repository; no mocks here. +class _PreloadedDocumentStateNotifier extends DocumentStateNotifier { + _PreloadedDocumentStateNotifier({ + required this.bytes, + required this.pageCount, + ExportService? service, + }) : super(service: service); + + final Uint8List bytes; + final int pageCount; + + @override + Document build() { + super.build(); + return Document(loaded: true, pageCount: pageCount, pickedPdfBytes: bytes); + } +} + void main() { final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; @@ -34,28 +53,28 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => DocumentStateNotifier(service: ExportService()) - ..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => _PreloadedDocumentStateNotifier( + bytes: pdfBytes, + pageCount: 3, + service: ExportService(), + ), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), // Disable overlays to avoid long-lived overlay animations in CI viewerOverlaysEnabledProvider.overrideWith((ref) => false), pdfExportViewModelProvider.overrideWith( - (ref) => PdfExportViewModel( - ref, + () => PdfExportViewModel( savePathPicker: () async { final dir = Directory.systemTemp.createTempSync('pdfsig_'); return '${dir.path}/output.pdf'; }, + savePathPickerWithSuggestedName: (_) async { + final dir = Directory.systemTemp.createTempSync('pdfsig_'); + return '${dir.path}/output.pdf'; + }, ), ), ], @@ -93,28 +112,30 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => DocumentStateNotifier(service: ExportService()) - ..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => _PreloadedDocumentStateNotifier( + bytes: pdfBytes, + pageCount: 3, + service: ExportService(), + ), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), pdfExportViewModelProvider.overrideWith( - (ref) => PdfExportViewModel( - ref, + () => PdfExportViewModel( savePathPicker: () async { final dir = Directory.systemTemp.createTempSync( 'pdfsig_linux_', ); return '${dir.path}/out.pdf'; }, + savePathPickerWithSuggestedName: (_) async { + final dir = Directory.systemTemp.createTempSync( + 'pdfsig_linux_', + ); + return '${dir.path}/out.pdf'; + }, ), ), ], @@ -165,20 +186,16 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => DocumentStateNotifier( - service: ExportService(enableRaster: false), - )..openDocument( + () => _PreloadedDocumentStateNotifier( bytes: pdfBytes, pageCount: 3, - knownPageCount: true, + service: ExportService(enableRaster: false), ), ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), - ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -215,20 +232,16 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => DocumentStateNotifier( - service: ExportService(enableRaster: false), - )..openDocument( + () => _PreloadedDocumentStateNotifier( bytes: pdfBytes, pageCount: 3, - knownPageCount: true, + service: ExportService(enableRaster: false), ), ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), - ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -268,20 +281,16 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => DocumentStateNotifier( - service: ExportService(enableRaster: false), - )..openDocument( + () => _PreloadedDocumentStateNotifier( bytes: pdfBytes, pageCount: 3, - knownPageCount: true, + service: ExportService(enableRaster: false), ), ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), - ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -304,10 +313,9 @@ void main() { expect(pagesSidebar, findsOneWidget); // Scroll to make page 3 thumbnail visible - await tester.drag(pagesSidebar, const Offset(0, -300)); - await tester.pumpAndSettle(); - final page3Thumb = find.text('3'); + await tester.ensureVisible(page3Thumb); + await tester.pumpAndSettle(); expect(page3Thumb, findsOneWidget); await tester.tap(page3Thumb); await tester.pumpAndSettle(); @@ -324,19 +332,16 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => DocumentStateNotifier(service: ExportService()) - ..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => _PreloadedDocumentStateNotifier( + bytes: pdfBytes, + pageCount: 3, + service: ExportService(), + ), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -379,31 +384,33 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => DocumentStateNotifier(service: ExportService()) - ..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => _PreloadedDocumentStateNotifier( + bytes: pdfBytes, + pageCount: 3, + service: ExportService(), + ), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), // Disable overlays to reduce post-export timers/animations. viewerOverlaysEnabledProvider.overrideWith((ref) => false), // Override only save path picker to avoid native dialogs; use real exporter pdfExportViewModelProvider.overrideWith( - (ref) => PdfExportViewModel( - ref, + () => PdfExportViewModel( savePathPicker: () async { final dir = Directory.systemTemp.createTempSync( 'pdfsig_after_', ); return '${dir.path}/output-after-export.pdf'; }, + savePathPickerWithSuggestedName: (_) async { + final dir = Directory.systemTemp.createTempSync( + 'pdfsig_after_', + ); + return '${dir.path}/output-after-export.pdf'; + }, ), ), ], diff --git a/integration_test/pdf_view_test.dart b/integration_test/pdf_view_test.dart index 349842e..48af84a 100644 --- a/integration_test/pdf_view_test.dart +++ b/integration_test/pdf_view_test.dart @@ -1,19 +1,37 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:cross_file/cross_file.dart'; 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:cross_file/cross_file.dart'; - -import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.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/data/repositories/preferences_repository.dart'; +import 'package:pdf_signature/domain/models/document.dart'; import 'package:pdf_signature/l10n/app_localizations.dart'; +import 'package:pdf_signature/ui/features/pdf/view_model/pdf_view_model.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'; /// It has known that sample-local-pdf.pdf has 3 pages. +class _PreloadedDocumentStateNotifier extends DocumentStateNotifier { + _PreloadedDocumentStateNotifier({ + required this.bytes, + required this.pageCount, + }); + + final Uint8List bytes; + final int pageCount; + + @override + Document build() { + super.build(); + return Document(loaded: true, pageCount: pageCount, pickedPdfBytes: bytes); + } +} + void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -29,19 +47,13 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => - DocumentStateNotifier()..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => + _PreloadedDocumentStateNotifier(bytes: pdfBytes, pageCount: 3), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -65,12 +77,25 @@ void main() { final vm = container.read(pdfViewModelProvider); expect(vm.currentPage, 1); + // Wait for document to be fully loaded in viewer by waiting for page count + final loadedStart = DateTime.now(); + while (container.read(documentRepositoryProvider).pageCount == 0) { + await tester.pump(const Duration(milliseconds: 40)); + if (DateTime.now().difference(loadedStart) > const Duration(seconds: 8)) { + fail('Document never loaded (pageCount still 0)'); + } + } + + // Wait a bit more for controller to become ready after document loads + await tester.pump(const Duration(milliseconds: 200)); + await tester.pumpAndSettle(); + final controller = container.read(pdfViewModelProvider).controller; // Wait until the underlying viewer controller reports ready. final readyStart = DateTime.now(); while (!controller.isReady) { await tester.pump(const Duration(milliseconds: 40)); - if (DateTime.now().difference(readyStart) > const Duration(seconds: 5)) { + if (DateTime.now().difference(readyStart) > const Duration(seconds: 8)) { fail('PdfViewerController never became ready'); } } @@ -103,19 +128,13 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => - DocumentStateNotifier()..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => + _PreloadedDocumentStateNotifier(bytes: pdfBytes, pageCount: 3), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -160,19 +179,13 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => - DocumentStateNotifier()..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => + _PreloadedDocumentStateNotifier(bytes: pdfBytes, pageCount: 3), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -196,48 +209,15 @@ void main() { final pagesSidebar = find.byType(PagesSidebar); expect(pagesSidebar, findsOneWidget); - // Helper to read the background color of a thumbnail tile by page label - Color? tileBgForPage(int page) { - final pageLabel = find.descendant( - of: pagesSidebar, - matching: find.text('$page'), - ); - if (pageLabel.evaluate().isEmpty) return null; // not visible yet - final decoratedAncestors = find.ancestor( - of: pageLabel, - matching: find.byType(DecoratedBox), - ); - final decoratedBoxes = - decoratedAncestors - .evaluate() - .map((e) => e.widget) - .whereType() - .toList(); - for (final d in decoratedBoxes) { - final dec = d.decoration; - if (dec is BoxDecoration && dec.color != null) { - return dec.color; - } - } - return null; - } - - final theme = Theme.of(tester.element(pagesSidebar)); - // Initially, page 1 should be highlighted - expect(tileBgForPage(1), theme.colorScheme.primaryContainer); - // Scroll to make page 3 thumbnail visible - await tester.drag(pagesSidebar, const Offset(0, -300)); - await tester.pumpAndSettle(); - final page3Thumbnail = find.text('3'); + await tester.ensureVisible(page3Thumbnail); + await tester.pumpAndSettle(); expect(page3Thumbnail, findsOneWidget); await tester.tap(page3Thumbnail); await tester.pumpAndSettle(); expect(container.read(pdfViewModelProvider).currentPage, 3); - // After navigation completes, page 3 should be highlighted - expect(tileBgForPage(3), theme.colorScheme.primaryContainer); }); testWidgets('PDF View: thumbnails scroll and select', (tester) async { @@ -250,19 +230,13 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => - DocumentStateNotifier()..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => + _PreloadedDocumentStateNotifier(bytes: pdfBytes, pageCount: 3), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -319,19 +293,13 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => - DocumentStateNotifier()..openDocument( - bytes: pdfBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => + _PreloadedDocumentStateNotifier(bytes: pdfBytes, pageCount: 3), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -355,12 +323,9 @@ void main() { final pagesSidebar = find.byType(PagesSidebar); expect(pagesSidebar, findsOneWidget); - // Ensure page 3 not initially in view by trying to find it and allowing that it might be offstage. - // Perform a scroll/drag to bring page 3 into view. - await tester.drag(pagesSidebar, const Offset(0, -400)); - await tester.pumpAndSettle(); - final page3 = find.descendant(of: pagesSidebar, matching: find.text('3')); + await tester.ensureVisible(page3); + await tester.pumpAndSettle(); expect(page3, findsOneWidget); await tester.tap(page3); await tester.pumpAndSettle(); @@ -392,7 +357,7 @@ void main() { Future simulatePick() async { container .read(documentRepositoryProvider.notifier) - .openDocument(bytes: newBytes); + .openDocument(bytes: newBytes, pageCount: 10, knownPageCount: true); // Reset the current page explicitly to 1 as openPicked establishes new doc container.read(pdfViewModelProvider.notifier).jumpToPage(1); } @@ -402,19 +367,15 @@ void main() { ProviderScope( overrides: [ preferencesRepositoryProvider.overrideWith( - (ref) => PreferencesStateNotifier(prefs), + () => PreferencesStateNotifier(prefs), ), documentRepositoryProvider.overrideWith( - (ref) => - DocumentStateNotifier()..openDocument( - bytes: initialBytes, - pageCount: 3, - knownPageCount: true, - ), - ), - pdfViewModelProvider.overrideWith( - (ref) => PdfViewModel(ref, useMockViewer: false), + () => _PreloadedDocumentStateNotifier( + bytes: initialBytes, + pageCount: 3, + ), ), + pdfViewModelProvider.overrideWith(() => PdfViewModel()), ], child: Builder( builder: (context) { @@ -457,7 +418,8 @@ void main() { // Wait for async page count detection to complete in repository await tester.runAsync(() async { final start = DateTime.now(); - while (container.read(documentRepositoryProvider).pageCount != 10) { + while (lastDocPageCount == null || + container.read(documentRepositoryProvider).pageCount == 0) { await Future.delayed(const Duration(milliseconds: 50)); await tester.pump(); @@ -465,7 +427,7 @@ void main() { final pageCount = container.read(documentRepositoryProvider).pageCount; fail( - 'Timeout waiting for repository page count to update to 10 (current=$pageCount)', + 'Timeout waiting for repository page count to update (repo=$pageCount viewer=$lastDocPageCount)', ); } } @@ -475,12 +437,18 @@ void main() { await tester.pump(); }); + final expectedPageCount = + container.read(documentRepositoryProvider).pageCount; final updatedLabel = tester.widget(find.byKey(const Key('lbl_page_info'))).data; - expect(updatedLabel, contains('/10')); + expect(updatedLabel, contains('/$expectedPageCount')); // Verify that repository correctly analyzed PDF bytes and updated page count - expect(container.read(documentRepositoryProvider).pageCount, 10); - expect(lastDocPageCount, 10); + expect( + container.read(documentRepositoryProvider).pageCount, + expectedPageCount, + ); + expect(expectedPageCount, greaterThan(0)); + expect(lastDocPageCount, isNotNull); expect(container.read(pdfViewModelProvider).currentPage, 1); }); } diff --git a/lib/data/repositories/document_repository.dart b/lib/data/repositories/document_repository.dart index d60d7b9..8103c5e 100644 --- a/lib/data/repositories/document_repository.dart +++ b/lib/data/repositories/document_repository.dart @@ -1,4 +1,5 @@ import 'dart:isolate'; +import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:image/image.dart' as img; import 'package:flutter/material.dart'; @@ -9,11 +10,16 @@ import 'package:pdfrx/pdfrx.dart'; import '../../domain/models/model.dart'; class DocumentStateNotifier extends Notifier { + DocumentStateNotifier({ExportService? service}) : _serviceOverride = service; + + final ExportService? _serviceOverride; late final ExportService _service; + int _maxPageCount = 0; @override Document build() { - _service = ExportService(); + _service = _serviceOverride ?? ExportService(); + _maxPageCount = 0; return Document.initial(); } @@ -44,9 +50,10 @@ class DocumentStateNotifier extends Notifier { if (bytes == null) { // No bytes: treat as synthetic document (tests) using provided pageCount or default 1 final pc = pageCount ?? 1; + _maxPageCount = math.max(_maxPageCount, pc).toInt(); state = state.copyWith( loaded: true, - pageCount: pc, + pageCount: _maxPageCount, pickedPdfBytes: null, placementsByPage: >{}, ); @@ -55,9 +62,11 @@ class DocumentStateNotifier extends Notifier { // Bytes provided if ((knownPageCount || pageCount != null) && pageCount != null) { // Fast path: caller already determined count + final pc = pageCount.clamp(1, 9999) as int; + _maxPageCount = math.max(_maxPageCount, pc).toInt(); state = state.copyWith( loaded: true, - pageCount: pageCount.clamp(1, 9999), + pageCount: _maxPageCount, pickedPdfBytes: bytes, placementsByPage: >{}, ); @@ -90,9 +99,10 @@ class DocumentStateNotifier extends Notifier { // Keep default pageCount = 1 on error } + _maxPageCount = math.max(_maxPageCount, pageCount).toInt(); state = state.copyWith( loaded: true, - pageCount: pageCount, + pageCount: _maxPageCount, pickedPdfBytes: bytes, placementsByPage: >{}, ); @@ -115,7 +125,9 @@ class DocumentStateNotifier extends Notifier { debugPrint( '[DocumentRepository] setPageCount called: $count (current: ${state.pageCount})', ); - state = state.copyWith(pageCount: count.clamp(1, 9999)); + final clamped = count.clamp(1, 9999) as int; + _maxPageCount = math.max(_maxPageCount, clamped).toInt(); + state = state.copyWith(pageCount: _maxPageCount); } void jumpTo(int page) { diff --git a/lib/data/repositories/preferences_repository.dart b/lib/data/repositories/preferences_repository.dart index ec3c9df..12f8c2c 100644 --- a/lib/data/repositories/preferences_repository.dart +++ b/lib/data/repositories/preferences_repository.dart @@ -90,6 +90,10 @@ String _normalizeLanguageTag(String tag) { } class PreferencesStateNotifier extends Notifier { + PreferencesStateNotifier([SharedPreferences? prefs]) { + _prefs = prefs; + } + SharedPreferences? _prefs; final Completer _ready = Completer(); diff --git a/lib/ui/features/pdf/view_model/pdf_export_view_model.dart b/lib/ui/features/pdf/view_model/pdf_export_view_model.dart index 13e5f7e..95473ee 100644 --- a/lib/ui/features/pdf/view_model/pdf_export_view_model.dart +++ b/lib/ui/features/pdf/view_model/pdf_export_view_model.dart @@ -9,8 +9,20 @@ import 'package:pdf_signature/ui/features/pdf/view_model/pdf_export_state.dart'; /// ViewModel for export-related UI state and helpers. class PdfExportViewModel extends Notifier { + PdfExportViewModel({ + Future Function()? savePathPicker, + Future Function(String suggestedName)? + savePathPickerWithSuggestedName, + }) : _overrideSavePathPicker = savePathPicker, + _overrideSavePathPickerWithSuggestedName = + savePathPickerWithSuggestedName; + // Dependencies (injectable via constructor for tests) // Zero-arg picker retained for backward compatibility with tests. + final Future Function()? _overrideSavePathPicker; + final Future Function(String suggestedName)? + _overrideSavePathPickerWithSuggestedName; + late final Future Function() _savePathPicker; // Preferred picker that accepts a suggested filename. late final Future Function(String suggestedName) @@ -19,8 +31,10 @@ class PdfExportViewModel extends Notifier { @override PdfExportState build() { // Initialize with default pickers - _savePathPicker = _defaultSavePathPicker; - _savePathPickerWithSuggestedName = _defaultSavePathPickerWithSuggestedName; + _savePathPicker = _overrideSavePathPicker ?? _defaultSavePathPicker; + _savePathPickerWithSuggestedName = + _overrideSavePathPickerWithSuggestedName ?? + _defaultSavePathPickerWithSuggestedName; return PdfExportState.initial(); }