feat: consolidate backend and docker-compose setup
This commit is contained in:
commit
ff3753a745
306 files changed
+35450
No files matched your search
@@ -0,0 +1,67 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:app_pfm_ocr_v2/features/camera/blur_detector.dart';
|
||||
|
||||
void main() {
|
||||
test('BlurDetector TDD algorithm test with document borders', () async {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final sharpFile = File('${tempDir.path}/sharp_doc.png');
|
||||
final blurFile = File('${tempDir.path}/blur_doc.png');
|
||||
|
||||
// 1. Generate a sharp document with a black background
|
||||
// Size: 200x200
|
||||
final sharpImg = img.Image(width: 200, height: 200);
|
||||
// Fill with black (background table)
|
||||
img.fill(sharpImg, color: img.ColorRgb8(0, 0, 0));
|
||||
|
||||
// Draw white document in the center (from 40 to 160)
|
||||
for (int y = 40; y < 160; y++) {
|
||||
for (int x = 40; x < 160; x++) {
|
||||
// Draw sharp alternating stripes inside the document to simulate sharp text
|
||||
final isStripe = (x % 6 < 3);
|
||||
final colorVal = isStripe ? 0 : 255;
|
||||
sharpImg.setPixelRgba(x, y, colorVal, colorVal, colorVal, 255);
|
||||
}
|
||||
}
|
||||
sharpFile.writeAsBytesSync(img.encodePng(sharpImg));
|
||||
|
||||
// 2. Generate a blurry document with the same black background
|
||||
final blurImg = img.Image(width: 200, height: 200);
|
||||
img.fill(blurImg, color: img.ColorRgb8(0, 0, 0));
|
||||
|
||||
// Draw white document in the center (from 40 to 160)
|
||||
for (int y = 40; y < 160; y++) {
|
||||
for (int x = 40; x < 160; x++) {
|
||||
// Draw solid white or very blurry gray stripes (low contrast)
|
||||
blurImg.setPixelRgba(x, y, 220, 220, 220, 255);
|
||||
}
|
||||
}
|
||||
// To make it look like a blurred document, we also blur the edges of the white card slightly
|
||||
final blurredDocImg = img.gaussianBlur(blurImg, radius: 2);
|
||||
blurFile.writeAsBytesSync(img.encodePng(blurredDocImg));
|
||||
|
||||
// 3. Run BlurDetector
|
||||
final sharpResult = await BlurDetector.checkBlur(sharpFile.path);
|
||||
final blurResult = await BlurDetector.checkBlur(blurFile.path);
|
||||
|
||||
print('TDD Sharp Doc Score: ${sharpResult.score} (Raw: ${sharpResult.rawScore}, IsBlur: ${sharpResult.isBlur})');
|
||||
print('TDD Blur Doc Score: ${blurResult.score} (Raw: ${blurResult.rawScore}, IsBlur: ${blurResult.isBlur})');
|
||||
|
||||
// 4. Assertions
|
||||
// The sharp document must be detected as sharp (score >= 80.0)
|
||||
expect(sharpResult.score, greaterThanOrEqualTo(80.0));
|
||||
expect(sharpResult.isBlur, isFalse);
|
||||
|
||||
// The blurry document must be detected as blurry (score < 80.0)
|
||||
expect(blurResult.score, lessThan(80.0));
|
||||
expect(blurResult.isBlur, isTrue);
|
||||
|
||||
// Clean up
|
||||
try {
|
||||
sharpFile.deleteSync();
|
||||
blurFile.deleteSync();
|
||||
tempDir.deleteSync();
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/camera/camera_screen.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/camera/image_preview_screen.dart';
|
||||
import 'package:app_pfm_ocr_v2/core/location/location_service.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('CameraScreen coordinates capture and preview navigation test', (WidgetTester tester) async {
|
||||
// Inject mock coordinates
|
||||
final mockPos = Position(
|
||||
latitude: -6.2000,
|
||||
longitude: 106.8000,
|
||||
timestamp: DateTime.now(),
|
||||
accuracy: 1.0,
|
||||
altitude: 10.0,
|
||||
altitudeAccuracy: 1.0,
|
||||
heading: 0.0,
|
||||
headingAccuracy: 1.0,
|
||||
speed: 0.0,
|
||||
speedAccuracy: 1.0,
|
||||
);
|
||||
LocationService.mockDeterminator = () async => mockPos;
|
||||
|
||||
// Create a dummy image file in temp directory
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final tempFile = File('${tempDir.path}/test_location_image.png');
|
||||
final dummyPngBytes = [
|
||||
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82,
|
||||
0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21, 196, 137,
|
||||
0, 0, 0, 10, 73, 68, 65, 84, 120, 156, 99, 0, 1, 0, 0, 5,
|
||||
0, 1, 13, 10, 45, 180, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
|
||||
];
|
||||
tempFile.writeAsBytesSync(dummyPngBytes);
|
||||
|
||||
// Build the widget tree
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: CameraScreen(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Find the state of the CameraScreen
|
||||
final CameraScreenState state = tester.state(find.byType(CameraScreen));
|
||||
|
||||
// Call the navigation method directly with coordinates
|
||||
state.navigateToPreview(
|
||||
tempFile.path,
|
||||
latitude: mockPos.latitude,
|
||||
longitude: mockPos.longitude,
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500)); // Pushes preview screen
|
||||
await tester.pump(const Duration(seconds: 1)); // Waits for blur analysis to finish
|
||||
|
||||
// Verify that the ImagePreviewScreen is displayed and has coordinates
|
||||
final previewFinder = find.byType(ImagePreviewScreen);
|
||||
expect(previewFinder, findsOneWidget);
|
||||
|
||||
final ImagePreviewScreen previewWidget = tester.widget(previewFinder);
|
||||
expect(previewWidget.latitude, equals(-6.2000));
|
||||
expect(previewWidget.longitude, equals(106.8000));
|
||||
|
||||
// Cleanup
|
||||
try {
|
||||
tempFile.deleteSync();
|
||||
tempDir.deleteSync();
|
||||
} catch (_) {}
|
||||
LocationService.mockDeterminator = null;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/camera/camera_screen.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/camera/image_preview_screen.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('CameraScreen navigation to ImagePreviewScreen test', (WidgetTester tester) async {
|
||||
// Generate a valid 1x1 PNG file in temp directory
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final tempFile = File('${tempDir.path}/test_image.png');
|
||||
final dummyPngBytes = [
|
||||
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82,
|
||||
0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21, 196, 137,
|
||||
0, 0, 0, 10, 73, 68, 65, 84, 120, 156, 99, 0, 1, 0, 0, 5,
|
||||
0, 1, 13, 10, 45, 180, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
|
||||
];
|
||||
tempFile.writeAsBytesSync(dummyPngBytes);
|
||||
|
||||
// Build a mock version of the widget tree with a simple navigator
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: CameraScreen(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Find the state of the CameraScreen
|
||||
final CameraScreenState state = tester.state(find.byType(CameraScreen));
|
||||
|
||||
// Call the navigation method directly to test the routing
|
||||
state.navigateToPreview(tempFile.path);
|
||||
|
||||
// Pump frames to allow navigation and async processing to complete
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500)); // Pushes preview screen
|
||||
await tester.pump(const Duration(seconds: 1)); // Waits for blur analysis to finish
|
||||
|
||||
// Verify that the ImagePreviewScreen is now displayed on top of the navigator
|
||||
expect(find.byType(ImagePreviewScreen), findsOneWidget);
|
||||
|
||||
// Cleanup
|
||||
try {
|
||||
tempFile.deleteSync();
|
||||
tempDir.deleteSync();
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/editor/editor_screen.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/documents/pending_documents_provider.dart';
|
||||
import 'package:app_pfm_ocr_v2/models/document_model.dart';
|
||||
import 'package:app_pfm_ocr_v2/main.dart';
|
||||
import 'package:app_pfm_ocr_v2/core/storage/local_storage.dart';
|
||||
|
||||
class MockLocalStorage extends LocalStorage {
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
@override
|
||||
Future<void> saveDocument(DocumentModel doc) async {}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('EditorScreen input validation tests', (WidgetTester tester) async {
|
||||
final mockLocalStorage = MockLocalStorage();
|
||||
|
||||
final testDoc = DocumentModel(
|
||||
id: 'pending_123',
|
||||
tanggal: '29 Juni 2026',
|
||||
noPo: 'PO/26/1234567890',
|
||||
noSo: '1234567890',
|
||||
noDo: 'DO-123',
|
||||
kepadaYth: 'PT. A',
|
||||
orderUntuk: 'PRIMA FRESH MART',
|
||||
alamat: 'Alamat',
|
||||
platTruk: 'B 1234 A',
|
||||
namaDriver: 'Driver',
|
||||
namaPenerima: '',
|
||||
latitude: -6.2000,
|
||||
longitude: 106.8000,
|
||||
items: [
|
||||
DocumentItem(nomorSku: '12010112', namaBarang: 'FIESTA CHICKEN NUGGET', banyak: '1 BOX', jumlah: '12 PAC'),
|
||||
],
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
|
||||
// Initialize the Riverpod container with local storage overrides
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
localStorageProvider.overrideWithValue(mockLocalStorage),
|
||||
],
|
||||
);
|
||||
|
||||
// Set the pending documents state BEFORE widget tree is built (before initState)
|
||||
container.read(pendingDocumentsProvider.notifier).state = [
|
||||
PendingDocument(
|
||||
id: 'pending_123',
|
||||
imagePath: 'dummy.png',
|
||||
status: PendingDocumentStatus.success,
|
||||
document: testDoc,
|
||||
latitude: -6.2000,
|
||||
longitude: 106.8000,
|
||||
),
|
||||
];
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: EditorScreen(pendingId: 'pending_123'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find all TextFormFields
|
||||
final textFormFields = find.byType(TextFormField);
|
||||
expect(textFormFields, findsAtLeastNWidgets(2));
|
||||
|
||||
// Enter invalid PO format (Index 0 is Tanggal, Index 1 is No PO)
|
||||
await tester.enterText(textFormFields.at(1), 'PO-INVALID');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger validation by tapping the button
|
||||
final reviewButtonFinder = find.text('Review Complete');
|
||||
expect(reviewButtonFinder, findsOneWidget);
|
||||
await tester.tap(reviewButtonFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify PO format validation error is shown
|
||||
expect(find.text('Format harus PO/[XX]/[XXXXXXXXXX]'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/auth/login_screen.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('LoginScreen validation tests', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: LoginScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Find the login button
|
||||
final loginButtonFinder = find.byType(ElevatedButton);
|
||||
expect(loginButtonFinder, findsOneWidget);
|
||||
|
||||
// Tap login button with empty inputs
|
||||
await tester.tap(loginButtonFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify inline validation error text is displayed
|
||||
expect(find.text('Username/ID cannot be empty'), findsOneWidget);
|
||||
expect(find.text('Password cannot be empty'), findsOneWidget);
|
||||
|
||||
// Enter a username, and a short password (e.g. 5 chars)
|
||||
final textFormFields = find.byType(TextFormField);
|
||||
expect(textFormFields, findsNWidgets(2));
|
||||
|
||||
await tester.enterText(textFormFields.first, 'admin');
|
||||
await tester.enterText(textFormFields.last, '12345');
|
||||
|
||||
// Tap the login button again
|
||||
await tester.tap(loginButtonFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify username warning is gone, but password length warning appears
|
||||
expect(find.text('Username/ID cannot be empty'), findsNothing);
|
||||
expect(find.text('Password must be at least 6 characters'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/documents/documents_screen.dart';
|
||||
import 'package:app_pfm_ocr_v2/models/document_model.dart';
|
||||
import 'package:app_pfm_ocr_v2/main.dart';
|
||||
import 'package:app_pfm_ocr_v2/core/storage/local_storage.dart';
|
||||
|
||||
class MockLocalStorage extends LocalStorage {
|
||||
final List<DocumentModel> docs;
|
||||
|
||||
MockLocalStorage(this.docs);
|
||||
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
List<DocumentModel> getAllDocuments() {
|
||||
return docs;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveDocument(DocumentModel doc) async {}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('DocumentsScreen search and coordinate formatting tests', (WidgetTester tester) async {
|
||||
final testDocs = [
|
||||
DocumentModel(
|
||||
id: '1',
|
||||
tanggal: '29 Juni 2026',
|
||||
noPo: 'PO/26/0000111111',
|
||||
noSo: '1000000001',
|
||||
noDo: 'DO-111111',
|
||||
kepadaYth: 'PT. PRIMAFOOD BEKASI',
|
||||
orderUntuk: 'PRIMA FRESH MART',
|
||||
alamat: 'Bekasi',
|
||||
platTruk: 'B 1234 ABC',
|
||||
namaDriver: 'Driver A',
|
||||
namaPenerima: 'Penerima A',
|
||||
latitude: -6.123456,
|
||||
longitude: 106.567890,
|
||||
items: [],
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
DocumentModel(
|
||||
id: '2',
|
||||
tanggal: '30 Juni 2026',
|
||||
noPo: 'PO/26/0000222222',
|
||||
noSo: '2000000002',
|
||||
noDo: 'DO-222222',
|
||||
kepadaYth: 'PT. PRIMAFOOD TANGERANG',
|
||||
orderUntuk: 'PRIMA FRESH MART',
|
||||
alamat: 'Tangerang',
|
||||
platTruk: 'B 5678 XYZ',
|
||||
namaDriver: 'Driver B',
|
||||
namaPenerima: 'Penerima B',
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
items: [],
|
||||
createdAt: DateTime.now().subtract(const Duration(hours: 1)),
|
||||
),
|
||||
];
|
||||
|
||||
final mockLocalStorage = MockLocalStorage(testDocs);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
localStorageProvider.overrideWithValue(mockLocalStorage),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
home: DocumentsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify both documents are rendered initially
|
||||
expect(find.text('DO: DO-111111'), findsOneWidget);
|
||||
expect(find.text('DO: DO-222222'), findsOneWidget);
|
||||
|
||||
// Verify coordinate rounding to 4 decimal places
|
||||
// -6.123456 -> -6.1235
|
||||
// 106.567890 -> 106.5679
|
||||
expect(find.text('Lat: -6.1235'), findsOneWidget);
|
||||
expect(find.text('Lng: 106.5679'), findsOneWidget);
|
||||
|
||||
// Type query in search bar to filter only Bekasi
|
||||
final searchField = find.byType(TextField);
|
||||
expect(searchField, findsOneWidget);
|
||||
await tester.enterText(searchField, 'BEKASI');
|
||||
await tester.pump();
|
||||
|
||||
// Verify only the Bekasi document matches
|
||||
expect(find.text('DO: DO-111111'), findsOneWidget);
|
||||
expect(find.text('DO: DO-222222'), findsNothing);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/camera/image_preview_screen.dart';
|
||||
import 'package:app_pfm_ocr_v2/features/camera/blur_detector.dart';
|
||||
|
||||
void main() {
|
||||
late Directory tempDir;
|
||||
late File tempFile;
|
||||
|
||||
setUpAll(() {
|
||||
tempDir = Directory.systemTemp.createTempSync();
|
||||
tempFile = File('${tempDir.path}/test_preview_image.png');
|
||||
final dummyPngBytes = [
|
||||
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82,
|
||||
0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21, 196, 137,
|
||||
0, 0, 0, 10, 73, 68, 65, 84, 120, 156, 99, 0, 1, 0, 0, 5,
|
||||
0, 1, 13, 10, 45, 180, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
|
||||
];
|
||||
tempFile.writeAsBytesSync(dummyPngBytes);
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
try {
|
||||
tempFile.deleteSync();
|
||||
tempDir.deleteSync();
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
testWidgets('ImagePreviewScreen shows Blur Warning when image is blurry', (WidgetTester tester) async {
|
||||
// Inject blurry mock result
|
||||
BlurDetector.mockResult = BlurResult(score: 50.0, isBlur: true, rawScore: 250.0);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: ImagePreviewScreen(imagePath: tempFile.path),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Wait for analysis to finish
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
// Verify warning text is displayed
|
||||
expect(find.text('Foto Terdeteksi Blur!'), findsOneWidget);
|
||||
expect(find.text('Kualitas Foto Baik'), findsNothing);
|
||||
|
||||
// Verify "Unggah Dokumen" button is disabled (has null onPressed)
|
||||
final uploadButton = tester.widget<ElevatedButton>(find.byType(ElevatedButton));
|
||||
expect(uploadButton.onPressed, isNull);
|
||||
|
||||
BlurDetector.mockResult = null;
|
||||
});
|
||||
|
||||
testWidgets('ImagePreviewScreen shows Sharp Success when image is clear', (WidgetTester tester) async {
|
||||
// Inject sharp mock result
|
||||
BlurDetector.mockResult = BlurResult(score: 95.0, isBlur: false, rawScore: 475.0);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: ImagePreviewScreen(imagePath: tempFile.path),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Wait for analysis to finish
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
// Verify success text is displayed
|
||||
expect(find.text('Kualitas Foto Baik'), findsOneWidget);
|
||||
expect(find.text('Foto Terdeteksi Blur!'), findsNothing);
|
||||
|
||||
// Verify "Unggah Dokumen" button is enabled (has non-null onPressed)
|
||||
final uploadButton = tester.widget<ElevatedButton>(find.byType(ElevatedButton));
|
||||
expect(uploadButton.onPressed, isNotNull);
|
||||
|
||||
BlurDetector.mockResult = null;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('Simple passing smoke test', () {
|
||||
expect(true, isTrue);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user