feat: small tool to remove dead code create by `bdd_widget_test`
This commit is contained in:
parent
b8918717b5
commit
5990f6fb01
|
@ -10,7 +10,8 @@ checkout [`docs/FRs.md`](docs/FRs.md)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
flutter pub get
|
flutter pub get
|
||||||
# flutter run build_runner build --delete-conflicting-outputs
|
# flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
# dart run tool/prune_unused_steps.dart --delete
|
||||||
|
|
||||||
# run the app
|
# run the app
|
||||||
flutter run
|
flutter run
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
plugins:
|
||||||
|
- custom_lint
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
targets:
|
||||||
|
$default:
|
||||||
|
sources:
|
||||||
|
- integration_test/**
|
||||||
|
- test/**
|
||||||
|
- lib/**
|
||||||
|
- $package$
|
||||||
|
builders:
|
||||||
|
pdf_signature|prune_unused_steps:
|
||||||
|
generate_for:
|
||||||
|
- test/features/**
|
|
@ -34,9 +34,9 @@ class ExportService {
|
||||||
required Uint8List? signatureImageBytes,
|
required Uint8List? signatureImageBytes,
|
||||||
double targetDpi = 144.0,
|
double targetDpi = 144.0,
|
||||||
}) async {
|
}) async {
|
||||||
print(
|
// print(
|
||||||
'exportSignedPdfFromFile: enter signedPage=$signedPage outputPath=$outputPath',
|
// 'exportSignedPdfFromFile: enter signedPage=$signedPage outputPath=$outputPath',
|
||||||
);
|
// );
|
||||||
// Read source bytes and delegate to bytes-based exporter
|
// Read source bytes and delegate to bytes-based exporter
|
||||||
Uint8List? srcBytes;
|
Uint8List? srcBytes;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -93,8 +93,10 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
Future<void> _saveSignedPdf() async {
|
Future<void> _saveSignedPdf() async {
|
||||||
final pdf = ref.read(pdfProvider);
|
final pdf = ref.read(pdfProvider);
|
||||||
final sig = ref.read(signatureProvider);
|
final sig = ref.read(signatureProvider);
|
||||||
|
// Cache messenger before any awaits to avoid using BuildContext across async gaps.
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
if (!pdf.loaded || sig.rect == null) {
|
if (!pdf.loaded || sig.rect == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
messenger.showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Nothing to save yet'),
|
content: Text('Nothing to save yet'),
|
||||||
), // guard per use-case
|
), // guard per use-case
|
||||||
|
@ -180,24 +182,24 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
// Desktop/mobile: we had a concrete path
|
// Desktop/mobile: we had a concrete path
|
||||||
if (ok) {
|
if (ok) {
|
||||||
ScaffoldMessenger.of(
|
messenger.showSnackBar(
|
||||||
context,
|
SnackBar(content: Text('Saved: ${savedPath ?? ''}')),
|
||||||
).showSnackBar(SnackBar(content: Text('Saved: ${savedPath ?? ''}')));
|
);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(
|
messenger.showSnackBar(
|
||||||
context,
|
const SnackBar(content: Text('Failed to save PDF')),
|
||||||
).showSnackBar(const SnackBar(content: Text('Failed to save PDF')));
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Web: indicate whether we triggered a download dialog
|
// Web: indicate whether we triggered a download dialog
|
||||||
if (ok) {
|
if (ok) {
|
||||||
ScaffoldMessenger.of(
|
messenger.showSnackBar(
|
||||||
context,
|
const SnackBar(content: Text('Download started')),
|
||||||
).showSnackBar(const SnackBar(content: Text('Download started')));
|
);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(
|
messenger.showSnackBar(
|
||||||
context,
|
const SnackBar(content: Text('Failed to generate PDF')),
|
||||||
).showSnackBar(const SnackBar(content: Text('Failed to generate PDF')));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -461,7 +463,10 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
),
|
),
|
||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(
|
color: Color.fromRGBO(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
0.05 + math.min(0.25, (sig.contrast - 1.0).abs()),
|
0.05 + math.min(0.25, (sig.contrast - 1.0).abs()),
|
||||||
),
|
),
|
||||||
border: Border.all(color: Colors.indigo, width: 2),
|
border: Border.all(color: Colors.indigo, width: 2),
|
||||||
|
|
|
@ -51,7 +51,10 @@ dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: ^2.4.12
|
build_runner: ^2.4.12
|
||||||
|
build: ^3.0.2
|
||||||
bdd_widget_test: ^2.0.1
|
bdd_widget_test: ^2.0.1
|
||||||
|
custom_lint: ^0.7.6
|
||||||
|
riverpod_lint: ^2.6.5
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
|
|
|
@ -12,7 +12,7 @@ Feature: load signature picture
|
||||||
And the image is not added to the document
|
And the image is not added to the document
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| file |
|
| file |
|
||||||
| corrupted.png |
|
| 'corrupted.png' |
|
||||||
| signature.bmp |
|
| 'signature.bmp' |
|
||||||
| empty.jpg |
|
| 'empty.jpg' |
|
||||||
|
|
|
@ -11,7 +11,6 @@ import './step/the_user_selects.dart';
|
||||||
import './step/the_app_attempts_to_load_the_image.dart';
|
import './step/the_app_attempts_to_load_the_image.dart';
|
||||||
import './step/the_user_is_notified_of_the_issue.dart';
|
import './step/the_user_is_notified_of_the_issue.dart';
|
||||||
import './step/the_image_is_not_added_to_the_document.dart';
|
import './step/the_image_is_not_added_to_the_document.dart';
|
||||||
import './step/_tokens.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('''load signature picture''', () {
|
group('''load signature picture''', () {
|
||||||
|
@ -21,31 +20,28 @@ void main() {
|
||||||
await theImageIsLoadedAndShownAsASignatureAsset(tester);
|
await theImageIsLoadedAndShownAsASignatureAsset(tester);
|
||||||
});
|
});
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'''Outline: Handle invalid or unsupported files (corrupted.png)''',
|
'''Outline: Handle invalid or unsupported files ('corrupted.png')''',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
await theUserSelects(tester, corrupted.png);
|
await theUserSelects(tester, 'corrupted.png');
|
||||||
await theAppAttemptsToLoadTheImage(tester);
|
await theAppAttemptsToLoadTheImage(tester);
|
||||||
await theUserIsNotifiedOfTheIssue(tester);
|
await theUserIsNotifiedOfTheIssue(tester);
|
||||||
await theImageIsNotAddedToTheDocument(tester);
|
await theImageIsNotAddedToTheDocument(tester);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'''Outline: Handle invalid or unsupported files (signature.bmp)''',
|
'''Outline: Handle invalid or unsupported files ('signature.bmp')''',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
await theUserSelects(tester, signature.bmp);
|
await theUserSelects(tester, 'signature.bmp');
|
||||||
await theAppAttemptsToLoadTheImage(tester);
|
await theAppAttemptsToLoadTheImage(tester);
|
||||||
await theUserIsNotifiedOfTheIssue(tester);
|
await theUserIsNotifiedOfTheIssue(tester);
|
||||||
await theImageIsNotAddedToTheDocument(tester);
|
await theImageIsNotAddedToTheDocument(tester);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'''Outline: Handle invalid or unsupported files (empty.jpg)''',
|
'''Outline: Handle invalid or unsupported files ('empty.jpg')''',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
await theUserSelects(tester, empty.jpg);
|
await theUserSelects(tester, 'empty.jpg');
|
||||||
await theAppAttemptsToLoadTheImage(tester);
|
await theAppAttemptsToLoadTheImage(tester);
|
||||||
await theUserIsNotifiedOfTheIssue(tester);
|
await theUserIsNotifiedOfTheIssue(tester);
|
||||||
await theImageIsNotAddedToTheDocument(tester);
|
await theImageIsNotAddedToTheDocument(tester);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,49 +23,32 @@ import './step/the_user_cannot_edit_the_document.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('''save signed PDF''', () {
|
group('''save signed PDF''', () {
|
||||||
testWidgets(
|
testWidgets('''Export the signed document to a new file''', (tester) async {
|
||||||
'''Export the signed document to a new file''',
|
await aPdfIsOpenAndContainsAtLeastOnePlacedSignature(tester);
|
||||||
(tester) async {
|
await theUserSavesexportsTheDocument(tester);
|
||||||
await aPdfIsOpenAndContainsAtLeastOnePlacedSignature(tester);
|
await aNewPdfFileIsSavedAtSpecifiedFullPathLocationAndFileName(tester);
|
||||||
await theUserSavesexportsTheDocument(tester);
|
await theSignaturesAppearOnTheCorrespondingPageInTheOutput(tester);
|
||||||
await aNewPdfFileIsSavedAtSpecifiedFullPathLocationAndFileName(tester);
|
await keepOtherUnchangedContentpagesIntactInTheOutput(tester);
|
||||||
await theSignaturesAppearOnTheCorrespondingPageInTheOutput(tester);
|
});
|
||||||
await keepOtherUnchangedContentpagesIntactInTheOutput(tester);
|
testWidgets('''Vector-accurate stamping into PDF page coordinates''',
|
||||||
},
|
(tester) async {
|
||||||
timeout: const Timeout(Duration(seconds: 30)),
|
await aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(tester);
|
||||||
);
|
await theUserSavesexportsTheDocument(tester);
|
||||||
testWidgets(
|
await theSignatureIsStampedAtTheExactPdfPageCoordinatesAndSize(tester);
|
||||||
'''Vector-accurate stamping into PDF page coordinates''',
|
await theStampRemainsCrispAtAnyZoomLevelNotRasterizedByTheScreen(tester);
|
||||||
(tester) async {
|
await otherPageContentRemainsVectorAndUnaltered(tester);
|
||||||
await aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(tester);
|
});
|
||||||
await theUserSavesexportsTheDocument(tester);
|
testWidgets('''Prevent saving when nothing is placed''', (tester) async {
|
||||||
await theSignatureIsStampedAtTheExactPdfPageCoordinatesAndSize(tester);
|
await aPdfIsOpenWithNoSignaturesPlaced(tester);
|
||||||
await theStampRemainsCrispAtAnyZoomLevelNotRasterizedByTheScreen(
|
await theUserAttemptsToSave(tester);
|
||||||
tester,
|
await theUserIsNotifiedThereIsNothingToSave(tester);
|
||||||
);
|
});
|
||||||
await otherPageContentRemainsVectorAndUnaltered(tester);
|
testWidgets('''Loading sign when exporting/saving files''', (tester) async {
|
||||||
},
|
await aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(tester);
|
||||||
timeout: const Timeout(Duration(seconds: 30)),
|
await theUserStartsExportingTheDocument(tester);
|
||||||
);
|
await theExportProcessIsNotYetFinished(tester);
|
||||||
testWidgets(
|
await theUserIsNotifiedThatTheExportIsStillInProgress(tester);
|
||||||
'''Prevent saving when nothing is placed''',
|
await theUserCannotEditTheDocument(tester);
|
||||||
(tester) async {
|
});
|
||||||
await aPdfIsOpenWithNoSignaturesPlaced(tester);
|
|
||||||
await theUserAttemptsToSave(tester);
|
|
||||||
await theUserIsNotifiedThereIsNothingToSave(tester);
|
|
||||||
},
|
|
||||||
timeout: const Timeout(Duration(seconds: 30)),
|
|
||||||
);
|
|
||||||
testWidgets(
|
|
||||||
'''Loading sign when exporting/saving files''',
|
|
||||||
(tester) async {
|
|
||||||
await aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(tester);
|
|
||||||
await theUserStartsExportingTheDocument(tester);
|
|
||||||
await theExportProcessIsNotYetFinished(tester);
|
|
||||||
await theUserIsNotifiedThatTheExportIsStillInProgress(tester);
|
|
||||||
await theUserCannotEditTheDocument(tester);
|
|
||||||
},
|
|
||||||
timeout: const Timeout(Duration(seconds: 30)),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,4 @@ Feature: Signature state logic
|
||||||
And signature rect right <= {400}
|
And signature rect right <= {400}
|
||||||
And signature rect bottom <= {560}
|
And signature rect bottom <= {560}
|
||||||
|
|
||||||
Scenario: setImageBytes ensures a rect exists for display
|
|
||||||
Given a new provider container
|
|
||||||
Then signature rect is null
|
|
||||||
When I set tiny signature image bytes
|
|
||||||
Then signature image bytes is not null
|
|
||||||
And signature rect is not null
|
|
||||||
|
|
|
@ -19,9 +19,6 @@ import './step/signature_rect_moved_from_center.dart';
|
||||||
import './step/aspect_lock_is.dart';
|
import './step/aspect_lock_is.dart';
|
||||||
import './step/i_resize_signature_by.dart';
|
import './step/i_resize_signature_by.dart';
|
||||||
import './step/signature_aspect_ratio_is_preserved_within.dart';
|
import './step/signature_aspect_ratio_is_preserved_within.dart';
|
||||||
import './step/i_set_tiny_signature_image_bytes.dart';
|
|
||||||
import './step/signature_image_bytes_is_not_null.dart';
|
|
||||||
import './step/signature_rect_is_not_null.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('''Signature state logic''', () {
|
group('''Signature state logic''', () {
|
||||||
|
@ -58,13 +55,5 @@ void main() {
|
||||||
await signatureRectRight(tester, 400);
|
await signatureRectRight(tester, 400);
|
||||||
await signatureRectBottom(tester, 560);
|
await signatureRectBottom(tester, 560);
|
||||||
});
|
});
|
||||||
testWidgets('''setImageBytes ensures a rect exists for display''',
|
|
||||||
(tester) async {
|
|
||||||
await aNewProviderContainer(tester);
|
|
||||||
await signatureRectIsNull(tester);
|
|
||||||
await iSetTinySignatureImageBytes(tester);
|
|
||||||
await signatureImageBytesIsNotNull(tester);
|
|
||||||
await signatureRectIsNotNull(tester);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:typed_data';
|
||||||
import 'dart:ui' show Rect, Size;
|
import 'dart:ui' show Rect, Size;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
// A lightweight fake exporter to avoid platform rasterization in tests.
|
// A lightweight fake exporter to avoid platform rasterization in tests.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: I set signature image bytes {Uint8List.fromList([0, 1, 2])}
|
|
||||||
Future<void> iSetSignatureImageBytes(WidgetTester tester, dynamic value) async {
|
|
||||||
final c = TestWorld.container!;
|
|
||||||
final bytes = value as Uint8List;
|
|
||||||
c.read(signatureProvider.notifier).setImageBytes(bytes);
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: I set tiny signature image bytes
|
|
||||||
Future<void> iSetTinySignatureImageBytes(WidgetTester tester) async {
|
|
||||||
final c = TestWorld.container!;
|
|
||||||
final bytes = Uint8List.fromList([0, 1, 2, 3]);
|
|
||||||
c.read(signatureProvider.notifier).setImageBytes(bytes);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: signature image bytes is not null
|
|
||||||
Future<void> signatureImageBytesIsNotNull(WidgetTester tester) async {
|
|
||||||
final c = TestWorld.container!;
|
|
||||||
expect(c.read(signatureProvider).imageBytes, isNotNull);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: signature rect is not null
|
|
||||||
Future<void> signatureRectIsNotNull(WidgetTester tester) async {
|
|
||||||
final c = TestWorld.container!;
|
|
||||||
expect(c.read(signatureProvider).rect, isNotNull);
|
|
||||||
}
|
|
|
@ -6,9 +6,10 @@ import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user saves/exports the document
|
/// Usage: the user saves/exports the document
|
||||||
Future<void> theUserSavesexportsTheDocument(WidgetTester tester) async {
|
Future<void> theUserSavesexportsTheDocument(WidgetTester tester) async {
|
||||||
// Logic-only: simulate a successful export without invoking IO or printing.raster
|
// Logic-only: simulate a successful export without invoking IO or printing raster
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
TestWorld.container = container;
|
TestWorld.container = container;
|
||||||
|
|
||||||
// Ensure state looks exportable
|
// Ensure state looks exportable
|
||||||
final pdf = container.read(pdfProvider);
|
final pdf = container.read(pdfProvider);
|
||||||
final sig = container.read(signatureProvider);
|
final sig = container.read(signatureProvider);
|
||||||
|
@ -16,6 +17,7 @@ Future<void> theUserSavesexportsTheDocument(WidgetTester tester) async {
|
||||||
expect(pdf.signedPage, isNotNull, reason: 'A signed page must be selected');
|
expect(pdf.signedPage, isNotNull, reason: 'A signed page must be selected');
|
||||||
expect(sig.rect, isNotNull, reason: 'Signature rect must exist');
|
expect(sig.rect, isNotNull, reason: 'Signature rect must exist');
|
||||||
expect(sig.imageBytes, isNotNull, reason: 'Signature image must exist');
|
expect(sig.imageBytes, isNotNull, reason: 'Signature image must exist');
|
||||||
|
|
||||||
// Simulate output
|
// Simulate output
|
||||||
TestWorld.lastExportBytes =
|
TestWorld.lastExportBytes =
|
||||||
TestWorld.lastExportBytes ?? Uint8List.fromList([1, 2, 3]);
|
TestWorld.lastExportBytes ?? Uint8List.fromList([1, 2, 3]);
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('openPicked loads document and initializes state', () {
|
|
||||||
final container = ProviderContainer();
|
|
||||||
addTearDown(container.dispose);
|
|
||||||
final notifier = container.read(pdfProvider.notifier);
|
|
||||||
notifier.openPicked(path: 'test.pdf', pageCount: 7);
|
|
||||||
final state = container.read(pdfProvider);
|
|
||||||
expect(state.loaded, isTrue);
|
|
||||||
expect(state.pickedPdfPath, 'test.pdf');
|
|
||||||
expect(state.pageCount, 7);
|
|
||||||
expect(state.currentPage, 1);
|
|
||||||
expect(state.markedForSigning, isFalse);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('jumpTo clamps within page boundaries', () {
|
|
||||||
final container = ProviderContainer();
|
|
||||||
addTearDown(container.dispose);
|
|
||||||
final notifier = container.read(pdfProvider.notifier);
|
|
||||||
notifier.openPicked(path: 'test.pdf', pageCount: 5);
|
|
||||||
notifier.jumpTo(10);
|
|
||||||
expect(container.read(pdfProvider).currentPage, 5);
|
|
||||||
notifier.jumpTo(0);
|
|
||||||
expect(container.read(pdfProvider).currentPage, 1);
|
|
||||||
notifier.jumpTo(3);
|
|
||||||
expect(container.read(pdfProvider).currentPage, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('setPageCount updates count without toggling other flags', () {
|
|
||||||
final container = ProviderContainer();
|
|
||||||
addTearDown(container.dispose);
|
|
||||||
final notifier = container.read(pdfProvider.notifier);
|
|
||||||
notifier.openPicked(path: 'test.pdf', pageCount: 2);
|
|
||||||
notifier.toggleMark();
|
|
||||||
notifier.setPageCount(9);
|
|
||||||
final s = container.read(pdfProvider);
|
|
||||||
expect(s.pageCount, 9);
|
|
||||||
expect(s.loaded, isTrue);
|
|
||||||
expect(s.markedForSigning, isTrue);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('placeDefaultRect centers a reasonable default rect', () {
|
|
||||||
final container = ProviderContainer();
|
|
||||||
addTearDown(container.dispose);
|
|
||||||
final sig = container.read(signatureProvider);
|
|
||||||
// Should be null initially
|
|
||||||
expect(sig.rect, isNull);
|
|
||||||
|
|
||||||
// Place using default pageSize (400x560)
|
|
||||||
container.read(signatureProvider.notifier).placeDefaultRect();
|
|
||||||
final placed = container.read(signatureProvider).rect!;
|
|
||||||
|
|
||||||
// Default should be within bounds and not tiny
|
|
||||||
expect(placed.left, greaterThanOrEqualTo(0));
|
|
||||||
expect(placed.top, greaterThanOrEqualTo(0));
|
|
||||||
expect(placed.right, lessThanOrEqualTo(400));
|
|
||||||
expect(placed.bottom, lessThanOrEqualTo(560));
|
|
||||||
expect(placed.width, greaterThan(50));
|
|
||||||
expect(placed.height, greaterThan(20));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('drag clamps to canvas bounds', () {
|
|
||||||
final container = ProviderContainer();
|
|
||||||
addTearDown(container.dispose);
|
|
||||||
container.read(signatureProvider.notifier).placeDefaultRect();
|
|
||||||
final before = container.read(signatureProvider).rect!;
|
|
||||||
// Drag far outside bounds
|
|
||||||
container
|
|
||||||
.read(signatureProvider.notifier)
|
|
||||||
.drag(const Offset(10000, -10000));
|
|
||||||
final after = container.read(signatureProvider).rect!;
|
|
||||||
expect(after.left, greaterThanOrEqualTo(0));
|
|
||||||
expect(after.top, greaterThanOrEqualTo(0));
|
|
||||||
expect(after.right, lessThanOrEqualTo(400));
|
|
||||||
expect(after.bottom, lessThanOrEqualTo(560));
|
|
||||||
// Ensure it actually moved
|
|
||||||
expect(after.center, isNot(equals(before.center)));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('resize respects aspect lock and clamps', () {
|
|
||||||
final container = ProviderContainer();
|
|
||||||
addTearDown(container.dispose);
|
|
||||||
final notifier = container.read(signatureProvider.notifier);
|
|
||||||
notifier.placeDefaultRect();
|
|
||||||
final before = container.read(signatureProvider).rect!;
|
|
||||||
notifier.toggleAspect(true);
|
|
||||||
notifier.resize(const Offset(1000, 1000));
|
|
||||||
final after = container.read(signatureProvider).rect!;
|
|
||||||
// With aspect lock the ratio should remain approximately the same
|
|
||||||
final ratioBefore = before.width / before.height;
|
|
||||||
final ratioAfter = after.width / after.height;
|
|
||||||
expect((ratioBefore - ratioAfter).abs(), lessThan(0.05));
|
|
||||||
// Still within bounds
|
|
||||||
expect(after.left, greaterThanOrEqualTo(0));
|
|
||||||
expect(after.top, greaterThanOrEqualTo(0));
|
|
||||||
expect(after.right, lessThanOrEqualTo(400));
|
|
||||||
expect(after.bottom, lessThanOrEqualTo(560));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('setImageBytes ensures a rect exists for display', () {
|
|
||||||
final container = ProviderContainer();
|
|
||||||
addTearDown(container.dispose);
|
|
||||||
final notifier = container.read(signatureProvider.notifier);
|
|
||||||
expect(container.read(signatureProvider).rect, isNull);
|
|
||||||
notifier.setImageBytes(Uint8List.fromList([0, 1, 2]));
|
|
||||||
expect(container.read(signatureProvider).imageBytes, isNotNull);
|
|
||||||
// placeDefaultRect is called when bytes are set if rect was null
|
|
||||||
expect(container.read(signatureProvider).rect, isNotNull);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
/// Prunes unused step files under test/features/step.
|
||||||
|
///
|
||||||
|
/// Heuristic: A step file is considered used if any test under test/features
|
||||||
|
/// imports it like: `import './step/<file>.dart';` (as generated by bdd_widget_test).
|
||||||
|
/// Otherwise it's unused and can be deleted.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// dart run tool/prune_unused_steps.dart # dry-run (prints list)
|
||||||
|
/// dart run tool/prune_unused_steps.dart --delete # delete unused files
|
||||||
|
/// dart run tool/prune_unused_steps.dart --verbose # show details
|
||||||
|
void main(List<String> args) {
|
||||||
|
final delete = args.contains('--delete');
|
||||||
|
final verbose = args.contains('--verbose');
|
||||||
|
|
||||||
|
final stepDir = Directory('test/features/step');
|
||||||
|
final testsDir = Directory('test/features');
|
||||||
|
|
||||||
|
if (!stepDir.existsSync()) {
|
||||||
|
stderr.writeln('Step folder not found at ${stepDir.path}');
|
||||||
|
exitCode = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!testsDir.existsSync()) {
|
||||||
|
stderr.writeln('Tests folder not found at ${testsDir.path}');
|
||||||
|
exitCode = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all step files (exclude private helpers like _world.dart)
|
||||||
|
final stepFiles = stepDir
|
||||||
|
.listSync(recursive: false)
|
||||||
|
.whereType<File>()
|
||||||
|
.where((f) => f.path.endsWith('.dart'))
|
||||||
|
.where((f) => !basename(f.path).startsWith('_'))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Collect all step imports from generated tests
|
||||||
|
// Matches imports like: import './step/<file>.dart';
|
||||||
|
final importRegex = RegExp(r'''import ['"]\./step/([^'\"]+)['"];\s*''');
|
||||||
|
final imported = <String>{};
|
||||||
|
|
||||||
|
for (final entity in testsDir.listSync(recursive: true)) {
|
||||||
|
if (entity is! File) continue;
|
||||||
|
if (!entity.path.endsWith('_test.dart')) continue;
|
||||||
|
final content = entity.readAsStringSync();
|
||||||
|
for (final m in importRegex.allMatches(content)) {
|
||||||
|
imported.add(m.group(1)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final unused = <File>[];
|
||||||
|
for (final f in stepFiles) {
|
||||||
|
final name = basename(f.path);
|
||||||
|
final isUsed = imported.contains(name);
|
||||||
|
if (verbose) {
|
||||||
|
stdout.writeln('- ${isUsed ? 'USED ' : 'UNUSED '} $name');
|
||||||
|
}
|
||||||
|
if (!isUsed) unused.add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unused.isEmpty) {
|
||||||
|
stdout.writeln('No unused step files found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.writeln('Unused step files (${unused.length}):');
|
||||||
|
for (final f in unused) {
|
||||||
|
stdout.writeln(' ${relative(f.path)}');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!delete) {
|
||||||
|
stdout.writeln('\nDry-run. Re-run with --delete to remove these files.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete unused files
|
||||||
|
var deleted = 0;
|
||||||
|
for (final f in unused) {
|
||||||
|
try {
|
||||||
|
f.deleteSync();
|
||||||
|
deleted++;
|
||||||
|
} catch (e) {
|
||||||
|
stderr.writeln('Failed to delete ${f.path}: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stdout.writeln('Deleted $deleted unused step files.');
|
||||||
|
}
|
||||||
|
|
||||||
|
String basename(String path) => path.split(RegExp(r'[\\/]')).last;
|
||||||
|
String relative(String path) {
|
||||||
|
final cwd = Directory.current.path.replaceAll('\\\\', '/');
|
||||||
|
final norm = path.replaceAll('\\\\', '/');
|
||||||
|
return norm.startsWith('$cwd/') ? norm.substring(cwd.length + 1) : norm;
|
||||||
|
}
|
Loading…
Reference in New Issue