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
|
||||
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
|
||||
flutter run
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# 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,
|
||||
double targetDpi = 144.0,
|
||||
}) async {
|
||||
print(
|
||||
'exportSignedPdfFromFile: enter signedPage=$signedPage outputPath=$outputPath',
|
||||
);
|
||||
// print(
|
||||
// 'exportSignedPdfFromFile: enter signedPage=$signedPage outputPath=$outputPath',
|
||||
// );
|
||||
// Read source bytes and delegate to bytes-based exporter
|
||||
Uint8List? srcBytes;
|
||||
try {
|
||||
|
|
|
@ -93,8 +93,10 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
Future<void> _saveSignedPdf() async {
|
||||
final pdf = ref.read(pdfProvider);
|
||||
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) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Nothing to save yet'),
|
||||
), // guard per use-case
|
||||
|
@ -180,24 +182,24 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
if (!kIsWeb) {
|
||||
// Desktop/mobile: we had a concrete path
|
||||
if (ok) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Saved: ${savedPath ?? ''}')));
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('Saved: ${savedPath ?? ''}')),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Failed to save PDF')));
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('Failed to save PDF')),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Web: indicate whether we triggered a download dialog
|
||||
if (ok) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Download started')));
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('Download started')),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Failed to generate PDF')));
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('Failed to generate PDF')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +463,10 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
|||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(
|
||||
color: Color.fromRGBO(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0.05 + math.min(0.25, (sig.contrast - 1.0).abs()),
|
||||
),
|
||||
border: Border.all(color: Colors.indigo, width: 2),
|
||||
|
|
|
@ -51,7 +51,10 @@ dev_dependencies:
|
|||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.4.12
|
||||
build: ^3.0.2
|
||||
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
|
||||
# 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
|
||||
|
||||
Examples:
|
||||
| file |
|
||||
| corrupted.png |
|
||||
| signature.bmp |
|
||||
| empty.jpg |
|
||||
| file |
|
||||
| 'corrupted.png' |
|
||||
| 'signature.bmp' |
|
||||
| '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_user_is_notified_of_the_issue.dart';
|
||||
import './step/the_image_is_not_added_to_the_document.dart';
|
||||
import './step/_tokens.dart';
|
||||
|
||||
void main() {
|
||||
group('''load signature picture''', () {
|
||||
|
@ -21,31 +20,28 @@ void main() {
|
|||
await theImageIsLoadedAndShownAsASignatureAsset(tester);
|
||||
});
|
||||
testWidgets(
|
||||
'''Outline: Handle invalid or unsupported files (corrupted.png)''',
|
||||
(tester) async {
|
||||
await theUserSelects(tester, corrupted.png);
|
||||
await theAppAttemptsToLoadTheImage(tester);
|
||||
await theUserIsNotifiedOfTheIssue(tester);
|
||||
await theImageIsNotAddedToTheDocument(tester);
|
||||
},
|
||||
);
|
||||
'''Outline: Handle invalid or unsupported files ('corrupted.png')''',
|
||||
(tester) async {
|
||||
await theUserSelects(tester, 'corrupted.png');
|
||||
await theAppAttemptsToLoadTheImage(tester);
|
||||
await theUserIsNotifiedOfTheIssue(tester);
|
||||
await theImageIsNotAddedToTheDocument(tester);
|
||||
});
|
||||
testWidgets(
|
||||
'''Outline: Handle invalid or unsupported files (signature.bmp)''',
|
||||
(tester) async {
|
||||
await theUserSelects(tester, signature.bmp);
|
||||
await theAppAttemptsToLoadTheImage(tester);
|
||||
await theUserIsNotifiedOfTheIssue(tester);
|
||||
await theImageIsNotAddedToTheDocument(tester);
|
||||
},
|
||||
);
|
||||
'''Outline: Handle invalid or unsupported files ('signature.bmp')''',
|
||||
(tester) async {
|
||||
await theUserSelects(tester, 'signature.bmp');
|
||||
await theAppAttemptsToLoadTheImage(tester);
|
||||
await theUserIsNotifiedOfTheIssue(tester);
|
||||
await theImageIsNotAddedToTheDocument(tester);
|
||||
});
|
||||
testWidgets(
|
||||
'''Outline: Handle invalid or unsupported files (empty.jpg)''',
|
||||
(tester) async {
|
||||
await theUserSelects(tester, empty.jpg);
|
||||
await theAppAttemptsToLoadTheImage(tester);
|
||||
await theUserIsNotifiedOfTheIssue(tester);
|
||||
await theImageIsNotAddedToTheDocument(tester);
|
||||
},
|
||||
);
|
||||
'''Outline: Handle invalid or unsupported files ('empty.jpg')''',
|
||||
(tester) async {
|
||||
await theUserSelects(tester, 'empty.jpg');
|
||||
await theAppAttemptsToLoadTheImage(tester);
|
||||
await theUserIsNotifiedOfTheIssue(tester);
|
||||
await theImageIsNotAddedToTheDocument(tester);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,49 +23,32 @@ import './step/the_user_cannot_edit_the_document.dart';
|
|||
|
||||
void main() {
|
||||
group('''save signed PDF''', () {
|
||||
testWidgets(
|
||||
'''Export the signed document to a new file''',
|
||||
(tester) async {
|
||||
await aPdfIsOpenAndContainsAtLeastOnePlacedSignature(tester);
|
||||
await theUserSavesexportsTheDocument(tester);
|
||||
await aNewPdfFileIsSavedAtSpecifiedFullPathLocationAndFileName(tester);
|
||||
await theSignaturesAppearOnTheCorrespondingPageInTheOutput(tester);
|
||||
await keepOtherUnchangedContentpagesIntactInTheOutput(tester);
|
||||
},
|
||||
timeout: const Timeout(Duration(seconds: 30)),
|
||||
);
|
||||
testWidgets(
|
||||
'''Vector-accurate stamping into PDF page coordinates''',
|
||||
(tester) async {
|
||||
await aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(tester);
|
||||
await theUserSavesexportsTheDocument(tester);
|
||||
await theSignatureIsStampedAtTheExactPdfPageCoordinatesAndSize(tester);
|
||||
await theStampRemainsCrispAtAnyZoomLevelNotRasterizedByTheScreen(
|
||||
tester,
|
||||
);
|
||||
await otherPageContentRemainsVectorAndUnaltered(tester);
|
||||
},
|
||||
timeout: const Timeout(Duration(seconds: 30)),
|
||||
);
|
||||
testWidgets(
|
||||
'''Prevent saving when nothing is placed''',
|
||||
(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)),
|
||||
);
|
||||
testWidgets('''Export the signed document to a new file''', (tester) async {
|
||||
await aPdfIsOpenAndContainsAtLeastOnePlacedSignature(tester);
|
||||
await theUserSavesexportsTheDocument(tester);
|
||||
await aNewPdfFileIsSavedAtSpecifiedFullPathLocationAndFileName(tester);
|
||||
await theSignaturesAppearOnTheCorrespondingPageInTheOutput(tester);
|
||||
await keepOtherUnchangedContentpagesIntactInTheOutput(tester);
|
||||
});
|
||||
testWidgets('''Vector-accurate stamping into PDF page coordinates''',
|
||||
(tester) async {
|
||||
await aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(tester);
|
||||
await theUserSavesexportsTheDocument(tester);
|
||||
await theSignatureIsStampedAtTheExactPdfPageCoordinatesAndSize(tester);
|
||||
await theStampRemainsCrispAtAnyZoomLevelNotRasterizedByTheScreen(tester);
|
||||
await otherPageContentRemainsVectorAndUnaltered(tester);
|
||||
});
|
||||
testWidgets('''Prevent saving when nothing is placed''', (tester) async {
|
||||
await aPdfIsOpenWithNoSignaturesPlaced(tester);
|
||||
await theUserAttemptsToSave(tester);
|
||||
await theUserIsNotifiedThereIsNothingToSave(tester);
|
||||
});
|
||||
testWidgets('''Loading sign when exporting/saving files''', (tester) async {
|
||||
await aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(tester);
|
||||
await theUserStartsExportingTheDocument(tester);
|
||||
await theExportProcessIsNotYetFinished(tester);
|
||||
await theUserIsNotifiedThatTheExportIsStillInProgress(tester);
|
||||
await theUserCannotEditTheDocument(tester);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -32,9 +32,4 @@ Feature: Signature state logic
|
|||
And signature rect right <= {400}
|
||||
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/i_resize_signature_by.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() {
|
||||
group('''Signature state logic''', () {
|
||||
|
@ -58,13 +55,5 @@ void main() {
|
|||
await signatureRectRight(tester, 400);
|
||||
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:io';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||
import '_world.dart';
|
||||
|
||||
// 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:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||
import '_world.dart';
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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
|
||||
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();
|
||||
TestWorld.container = container;
|
||||
|
||||
// Ensure state looks exportable
|
||||
final pdf = container.read(pdfProvider);
|
||||
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(sig.rect, isNotNull, reason: 'Signature rect must exist');
|
||||
expect(sig.imageBytes, isNotNull, reason: 'Signature image must exist');
|
||||
|
||||
// Simulate output
|
||||
TestWorld.lastExportBytes =
|
||||
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