import 'package:fabled_app/data/api/voice_api.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:fabled_app/data/models/news_item.dart'; import 'package:fabled_app/data/models/briefing_feed.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('NewsItem.fromJson', () { test('parses all fields', () { final json = { 'id': 42, 'title': 'Big news', 'url': 'https://example.com/article', 'snippet': 'A short summary.', 'source': 'Example News', 'published_at': '2026-01-15T10:00:00', 'topics': ['tech', 'ai'], 'reaction': 'up', }; final item = NewsItem.fromJson(json); expect(item.id, equals(42)); expect(item.title, equals('Big news')); expect(item.url, equals('https://example.com/article')); expect(item.snippet, equals('A short summary.')); expect(item.source, equals('Example News')); expect(item.publishedAt, equals(DateTime.parse('2026-01-15T10:00:00'))); expect(item.topics, equals(['tech', 'ai'])); expect(item.reaction, equals('up')); }); test('handles null published_at and reaction', () { final json = { 'id': 1, 'title': '', 'url': '', 'snippet': '', 'source': '', 'published_at': null, 'topics': [], 'reaction': null, }; final item = NewsItem.fromJson(json); expect(item.publishedAt, isNull); expect(item.reaction, isNull); expect(item.topics, isEmpty); }); }); group('BriefingFeed.fromJson', () { test('parses all fields', () { final json = { 'id': 7, 'title': 'Hacker News', 'url': 'https://news.ycombinator.com/rss', 'category': 'tech', }; final feed = BriefingFeed.fromJson(json); expect(feed.id, equals(7)); expect(feed.title, equals('Hacker News')); expect(feed.url, equals('https://news.ycombinator.com/rss')); expect(feed.category, equals('tech')); }); test('handles null category', () { final json = { 'id': 8, 'title': 'Feed', 'url': 'https://example.com/rss', 'category': null, }; final feed = BriefingFeed.fromJson(json); expect(feed.category, isNull); }); }); }