import 'package:fabled_app/data/models/calendar_event.dart'; import 'package:fabled_app/data/api/voice_api.dart'; import 'package:fabled_app/data/repositories/write_queue.dart'; import 'package:fabled_app/providers/voice_provider.dart'; import 'package:fabled_app/data/models/knowledge_item.dart'; import 'package:fabled_app/data/models/note.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('Note.fromJson', () { test('parses noteType when present', () { final json = { 'id': 1, 'title': 'Alice', 'body': '', 'tags': [], 'note_type': 'person', 'created_at': '2024-01-01T00:00:00', 'updated_at': '2024-01-01T00:00:00', }; final note = Note.fromJson(json); expect(note.noteType, equals('person')); }); test('defaults noteType to note when absent', () { final json = { 'id': 2, 'title': 'My note', 'body': '', 'tags': [], 'created_at': '2024-01-01T00:00:00', 'updated_at': '2024-01-01T00:00:00', }; final note = Note.fromJson(json); expect(note.noteType, equals('note')); }); }); group('KnowledgeItem.fromJson', () { test('parses a task item with task fields', () { final json = { 'id': 10, 'note_type': 'task', 'title': 'Do the thing', 'body': '', 'tags': [], 'status': 'todo', 'priority': 'high', 'due_date': '2025-01-01', 'created_at': '2024-01-01T00:00:00', 'updated_at': '2024-01-01T00:00:00', }; final item = KnowledgeItem.fromJson(json); expect(item.noteType, equals('task')); expect(item.status, equals('todo')); expect(item.priority, equals('high')); }); test('defaults noteType to note when absent', () { final json = { 'id': 11, 'title': 'A note', 'body': '', 'tags': [], 'created_at': '2024-01-01T00:00:00', 'updated_at': '2024-01-01T00:00:00', }; final item = KnowledgeItem.fromJson(json); expect(item.noteType, equals('note')); expect(item.status, isNull); }); }); group('VoiceStatus.fromJson', () { test('parses enabled with stt and tts available', () { final status = VoiceStatus.fromJson({ 'enabled': true, 'stt': true, 'tts': true, }); expect(status.enabled, isTrue); expect(status.stt, isTrue); expect(status.tts, isTrue); }); test('parses disabled state', () { final status = VoiceStatus.fromJson({ 'enabled': false, 'stt': false, 'tts': false, }); expect(status.enabled, isFalse); }); test('fullyAvailable is false when enabled but stt is false', () { final status = VoiceStatus.fromJson({ 'enabled': true, 'stt': false, 'tts': true, }); expect(status.fullyAvailable, isFalse); }); }); group('VoiceNotifier sentence extraction', () { test('extracts complete sentences at full stops', () { final result = extractSentences('Hello world. How are you? I am fine!'); expect(result.sentences, equals(['Hello world.', 'How are you?', 'I am fine!'])); expect(result.remainder, equals('')); }); test('leaves incomplete fragment in remainder', () { final result = extractSentences('Hello world. Incomplete'); expect(result.sentences, equals(['Hello world.'])); expect(result.remainder, equals('Incomplete')); }); test('returns empty sentences and full text when no boundary', () { final result = extractSentences('No boundary here'); expect(result.sentences, isEmpty); expect(result.remainder, equals('No boundary here')); }); }); group('VoiceNotifier markdown stripping', () { test('strips code fences', () { expect(stripMarkdownForTts('Before\n```dart\ncode\n```\nAfter'), equals('Before After')); }); test('strips bold and italic markers', () { expect(stripMarkdownForTts('**bold** and *italic*'), equals('bold and italic')); }); test('strips headers', () { expect(stripMarkdownForTts('## Section title'), equals('Section title')); }); test('keeps link text, removes URL', () { expect(stripMarkdownForTts('[click here](https://example.com)'), equals('click here')); }); test('strips list markers', () { expect(stripMarkdownForTts('- item one\n- item two'), equals('item one item two')); }); }); group('CalendarEvent.fromJson', () { test('parses all fields', () { final json = { 'id': 10, 'title': 'Team meeting', 'start_dt': '2026-04-07T09:00:00+00:00', 'end_dt': '2026-04-07T10:00:00+00:00', 'all_day': false, 'description': 'Weekly sync', 'location': 'Room 4', 'color': '#6366F1', 'recurrence': 'FREQ=WEEKLY', 'project_id': 3, 'reminder_minutes': 15, }; final event = CalendarEvent.fromJson(json); expect(event.id, equals(10)); expect(event.title, equals('Team meeting')); expect(event.startDt, equals(DateTime.parse('2026-04-07T09:00:00+00:00').toLocal())); expect(event.endDt, equals(DateTime.parse('2026-04-07T10:00:00+00:00').toLocal())); expect(event.allDay, isFalse); expect(event.description, equals('Weekly sync')); expect(event.location, equals('Room 4')); expect(event.color, equals('#6366F1')); expect(event.recurrence, equals('FREQ=WEEKLY')); expect(event.projectId, equals(3)); expect(event.reminderMinutes, equals(15)); }); test('handles null optional fields', () { final json = { 'id': 11, 'title': 'Birthday', 'start_dt': '2026-05-01T00:00:00+00:00', 'end_dt': null, 'all_day': true, 'description': '', 'location': '', 'color': '', 'recurrence': null, 'project_id': null, 'reminder_minutes': null, }; final event = CalendarEvent.fromJson(json); expect(event.endDt, isNull); expect(event.allDay, isTrue); expect(event.recurrence, isNull); expect(event.projectId, isNull); expect(event.reminderMinutes, isNull); }); }); group('dateOnly', () { test('strips time from datetime', () { final dt = DateTime(2026, 4, 7, 14, 30, 45); expect(dateOnly(dt), equals(DateTime(2026, 4, 7))); }); }); group('QueueFailure.message', () { test('overwritten with title quotes the title', () { final f = QueueFailure( reason: QueueFailureReason.overwritten, domain: 'notes', title: 'Grocery list', ); expect(f.message, contains('"Grocery list"')); expect(f.message, contains('overwritten')); }); test('rejected without title falls back to generic phrasing', () { final f = QueueFailure( reason: QueueFailureReason.rejected, domain: 'tasks', detail: 'title required', ); expect(f.message, contains('an offline edit')); expect(f.message, contains('title required')); }); test('missing communicates server-side deletion', () { final f = QueueFailure( reason: QueueFailureReason.missing, domain: 'projects', title: 'Q2 launch', ); expect(f.message, contains('"Q2 launch"')); expect(f.message, contains('deleted on the server')); }); }); }