280 lines
8.5 KiB
Dart
280 lines
8.5 KiB
Dart
import 'package:fabled_app/data/models/calendar_event.dart';
|
|
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': <dynamic>[],
|
|
'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': <dynamic>[],
|
|
'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': <dynamic>[],
|
|
'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': <dynamic>[],
|
|
'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': <dynamic>[],
|
|
'reaction': null,
|
|
};
|
|
final item = NewsItem.fromJson(json);
|
|
expect(item.publishedAt, isNull);
|
|
expect(item.reaction, isNull);
|
|
expect(item.topics, isEmpty);
|
|
});
|
|
});
|
|
|
|
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('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);
|
|
});
|
|
});
|
|
}
|