summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/home_screen_widget_test.dart44
-rw-r--r--test/log_service_test.dart67
-rw-r--r--test/shared_text_handler_test.dart147
3 files changed, 258 insertions, 0 deletions
diff --git a/test/home_screen_widget_test.dart b/test/home_screen_widget_test.dart
new file mode 100644
index 0000000..c05d49b
--- /dev/null
+++ b/test/home_screen_widget_test.dart
@@ -0,0 +1,44 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:quicklog/screens/home_screen.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ testWidgets('character counter updates as user types', (tester) async {
+ SharedPreferences.setMockInitialValues(<String, Object>{});
+ await tester.pumpWidget(const MaterialApp(home: HomeScreen()));
+ await tester.pumpAndSettle();
+
+ expect(find.text('0 chars'), findsOneWidget);
+
+ await tester.enterText(find.byType(TextField), 'hello');
+ await tester.pump();
+ expect(find.text('5 chars'), findsOneWidget);
+ });
+
+ testWidgets('Clear button empties the input', (tester) async {
+ SharedPreferences.setMockInitialValues(<String, Object>{});
+ await tester.pumpWidget(const MaterialApp(home: HomeScreen()));
+ await tester.pumpAndSettle();
+
+ await tester.enterText(find.byType(TextField), 'something');
+ await tester.pump();
+ expect(find.text('9 chars'), findsOneWidget);
+
+ await tester.tap(find.text('Clear'));
+ await tester.pump();
+ expect(find.text('0 chars'), findsOneWidget);
+ });
+
+ testWidgets('Log text button is rendered and enabled', (tester) async {
+ SharedPreferences.setMockInitialValues(<String, Object>{});
+ await tester.pumpWidget(const MaterialApp(home: HomeScreen()));
+ await tester.pumpAndSettle();
+
+ final btn = find.widgetWithText(FilledButton, 'Log text');
+ expect(btn, findsOneWidget);
+ expect(tester.widget<FilledButton>(btn).enabled, isTrue);
+ });
+}
diff --git a/test/log_service_test.dart b/test/log_service_test.dart
new file mode 100644
index 0000000..969d1f8
--- /dev/null
+++ b/test/log_service_test.dart
@@ -0,0 +1,67 @@
+import 'dart:io';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:path/path.dart' as p;
+import 'package:quicklog/services/log_service.dart';
+
+void main() {
+ group('logEntry', () {
+ late Directory tmp;
+
+ setUp(() async {
+ tmp = await Directory.systemTemp.createTemp('ql-test-');
+ });
+
+ tearDown(() async {
+ if (await tmp.exists()) await tmp.delete(recursive: true);
+ });
+
+ test('writes a file with ql-YYMMDD-HHMMSS.md filename pattern', () async {
+ final f = await logEntry(tmp.path, 'hello');
+ expect(p.basename(f.path), matches(RegExp(r'^ql-\d{6}-\d{6}\.md$')));
+ expect(await f.readAsString(), 'hello');
+ });
+
+ test('preserves arbitrary content including unicode and newlines', () async {
+ const text = 'line 1\nläine 2\n第三行';
+ final f = await logEntry(tmp.path, text);
+ expect(await f.readAsString(), text);
+ });
+
+ test('uses the provided timestamp when given', () async {
+ final ts = DateTime(2026, 5, 7, 14, 30, 45);
+ final f = await logEntry(tmp.path, 'x', now: ts);
+ expect(p.basename(f.path), 'ql-260507-143045.md');
+ });
+
+ test('throws on a non-existent directory', () async {
+ final bad = p.join(tmp.path, 'does', 'not', 'exist');
+ expect(() => logEntry(bad, 'x'), throwsA(isA<FileSystemException>()));
+ });
+ });
+
+ group('listEntries', () {
+ late Directory tmp;
+ setUp(() async => tmp = await Directory.systemTemp.createTemp('ql-list-'));
+ tearDown(() async {
+ if (await tmp.exists()) await tmp.delete(recursive: true);
+ });
+
+ test('returns entries sorted newest first and skips malformed names', () async {
+ await File(p.join(tmp.path, 'ql-260101-000000.md')).writeAsString('a');
+ await File(p.join(tmp.path, 'ql-260102-000000.md')).writeAsString('b');
+ await File(p.join(tmp.path, 'random.md')).writeAsString('skip');
+ await File(p.join(tmp.path, 'ql-bad-format.md')).writeAsString('skip');
+
+ final entries = await listEntries(tmp.path);
+ expect(entries.length, 2);
+ expect(p.basename(entries[0].file.path), 'ql-260102-000000.md');
+ expect(p.basename(entries[1].file.path), 'ql-260101-000000.md');
+ });
+
+ test('returns empty list for missing directory', () async {
+ final entries = await listEntries(p.join(tmp.path, 'nope'));
+ expect(entries, isEmpty);
+ });
+ });
+}
diff --git a/test/shared_text_handler_test.dart b/test/shared_text_handler_test.dart
new file mode 100644
index 0000000..e0940e3
--- /dev/null
+++ b/test/shared_text_handler_test.dart
@@ -0,0 +1,147 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:quicklog/services/shared_text_handler.dart';
+
+void main() {
+ group('prepareSharedTextLoad', () {
+ test('rejects whitespace-only input', () {
+ final d = prepareSharedTextLoad(' \n\t', false);
+ expect(d.proceed, isFalse);
+ });
+
+ test('returns prefill mode when autoLog is false', () {
+ final d = prepareSharedTextLoad('hello', false);
+ expect(d.proceed, isTrue);
+ expect(d.mode, SharedTextLoadMode.prefill);
+ expect(d.text, 'hello');
+ });
+
+ test('returns autoLog mode when autoLog is true', () {
+ final d = prepareSharedTextLoad('hello', true);
+ expect(d.proceed, isTrue);
+ expect(d.mode, SharedTextLoadMode.autoLog);
+ });
+
+ test('does not truncate long input', () {
+ final big = 'x' * 10000;
+ final d = prepareSharedTextLoad(big, true);
+ expect(d.text.length, 10000);
+ });
+ });
+
+ group('handleSharedTextLoad', () {
+ late _Probe p;
+ setUp(() => p = _Probe());
+
+ test('autoLog success: logs, shows info, resets, clears cache', () async {
+ await handleSharedTextLoad(
+ text: 'note',
+ autoLog: true,
+ dir: '/tmp',
+ prefill: p.prefill,
+ focus: p.focus,
+ resetInput: p.resetInput,
+ clearCache: p.clearCache,
+ logFn: (_, _) async => p.logged = true,
+ showInfo: p.showInfo,
+ showError: p.showError,
+ );
+ expect(p.logged, true);
+ expect(p.info, ['Logged']);
+ expect(p.didReset, true);
+ expect(p.cleared, true);
+ expect(p.errors, isEmpty);
+ });
+
+ test('autoLog failure: shows error, keeps cache, does not reset', () async {
+ await handleSharedTextLoad(
+ text: 'note',
+ autoLog: true,
+ dir: '/tmp',
+ prefill: p.prefill,
+ focus: p.focus,
+ resetInput: p.resetInput,
+ clearCache: p.clearCache,
+ logFn: (_, _) async => throw Exception('boom'),
+ showInfo: p.showInfo,
+ showError: p.showError,
+ );
+ expect(p.errors.length, 1);
+ expect(p.cleared, false);
+ expect(p.didReset, false);
+ });
+
+ test('empty text: clears cache and skips everything else', () async {
+ await handleSharedTextLoad(
+ text: ' ',
+ autoLog: true,
+ dir: '/tmp',
+ prefill: p.prefill,
+ focus: p.focus,
+ resetInput: p.resetInput,
+ clearCache: p.clearCache,
+ logFn: (_, _) async {
+ p.logged = true;
+ },
+ showInfo: p.showInfo,
+ showError: p.showError,
+ );
+ expect(p.logged, false);
+ expect(p.cleared, true);
+ expect(p.didReset, false);
+ expect(p.prefilled, isNull);
+ });
+
+ test('prefill mode: prefills + focuses + clears cache, no log', () async {
+ await handleSharedTextLoad(
+ text: 'note',
+ autoLog: false,
+ dir: '/tmp',
+ prefill: p.prefill,
+ focus: p.focus,
+ resetInput: p.resetInput,
+ clearCache: p.clearCache,
+ logFn: (_, _) async => p.logged = true,
+ showInfo: p.showInfo,
+ showError: p.showError,
+ );
+ expect(p.prefilled, 'note');
+ expect(p.focused, true);
+ expect(p.cleared, true);
+ expect(p.logged, false);
+ });
+ });
+}
+
+class _Probe {
+ String? prefilled;
+ bool focused = false;
+ bool didReset = false;
+ bool cleared = false;
+ bool logged = false;
+ final List<String> info = [];
+ final List<Object> errors = [];
+
+ void prefill(String s) {
+ prefilled = s;
+ }
+
+ void focus() {
+ focused = true;
+ }
+
+ void resetInput() {
+ didReset = true;
+ }
+
+ Future<void> clearCache() async {
+ cleared = true;
+ }
+
+ void showInfo(String title, String msg) {
+ info.add(title);
+ }
+
+ void showError(Object e) {
+ errors.add(e);
+ }
+}