import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:test/test.dart'; void main() { group('AchievementModel', () { group('constructor', () { test('creates model with default values', () { final model = AchievementModel(); expect(model.id, ''); expect(model.title, ''); expect(model.description, ''); expect(model.iconUrl, isNull); expect(model.unlockedAt, isNull); expect(model.type, AchievementType.firstWordLearned); expect(model.progress, 0.0); }); test('creates model with all parameters', () { final now = DateTime.now(); final model = AchievementModel( id: 'test_achievement', title: 'Test Achievement', description: 'Test Description', iconUrl: 'https://example.com/icon.png', unlockedAt: now, type: AchievementType.streak7Days, progress: 0.75, ); expect(model.id, 'test_achievement'); expect(model.title, 'Test Achievement'); expect(model.description, 'Test Description'); expect(model.iconUrl, 'https://example.com/icon.png'); expect(model.unlockedAt, now); expect(model.type, AchievementType.streak7Days); expect(model.progress, 0.75); }); test('creates model with null iconUrl', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', ); expect(model.iconUrl, isNull); }); test('creates model with null unlockedAt', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', ); expect(model.unlockedAt, isNull); }); }); group('toDto', () { test('converts model to DTO correctly', () { final now = DateTime.now(); final model = AchievementModel( id: 'test_achievement', title: 'Test Achievement', description: 'Test Description', iconUrl: 'https://example.com/icon.png', unlockedAt: now, type: AchievementType.words100Learned, progress: 0.85, ); final dto = model.toDto(); expect(dto.id, 'test_achievement'); expect(dto.title, 'Test Achievement'); expect(dto.description, 'Test Description'); expect(dto.iconUrl, 'https://example.com/icon.png'); expect(dto.unlockedAt, now); expect(dto.type, AchievementType.words100Learned); expect(dto.progress, 0.85); }); test('converts model with null values to DTO', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', ); final dto = model.toDto(); expect(dto.iconUrl, isNull); expect(dto.unlockedAt, isNull); }); test('converts all achievement types correctly', () { for (final type in AchievementType.values) { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', type: type, ); final dto = model.toDto(); expect(dto.type, type); } }); }); group('fromDto', () { test('creates model from DTO correctly', () { final now = DateTime.now(); final dto = AchievementDto( id: 'test_achievement', title: 'Test Achievement', description: 'Test Description', iconUrl: 'https://example.com/icon.png', unlockedAt: now, type: AchievementType.streak30Days, progress: 0.9, ); final model = AchievementModel.fromDto(dto); expect(model.id, 'test_achievement'); expect(model.title, 'Test Achievement'); expect(model.description, 'Test Description'); expect(model.iconUrl, 'https://example.com/icon.png'); expect(model.unlockedAt, now); expect(model.type, AchievementType.streak30Days); expect(model.progress, 0.9); }); test('creates model from DTO with null values', () { final dto = AchievementDto( id: 'test', title: 'Test', description: 'Test', type: AchievementType.firstWordLearned, ); final model = AchievementModel.fromDto(dto); expect(model.iconUrl, isNull); expect(model.unlockedAt, isNull); }); test('creates model from DTO for all achievement types', () { for (final type in AchievementType.values) { final dto = AchievementDto( id: 'test', title: 'Test', description: 'Test', type: type, ); final model = AchievementModel.fromDto(dto); expect(model.type, type); } }); }); group('toDto and fromDto roundtrip', () { test('preserves all data through conversion', () { final now = DateTime.now(); final original = AchievementModel( id: 'test_achievement', title: 'Test Achievement', description: 'Test Description', iconUrl: 'https://example.com/icon.png', unlockedAt: now, type: AchievementType.perfectTestScore, progress: 0.95, ); final dto = original.toDto(); final restored = AchievementModel.fromDto(dto); expect(restored.id, original.id); expect(restored.title, original.title); expect(restored.description, original.description); expect(restored.iconUrl, original.iconUrl); expect(restored.unlockedAt, original.unlockedAt); expect(restored.type, original.type); expect(restored.progress, original.progress); }); }); group('isUnlocked', () { test('returns false when unlockedAt is null', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', ); expect(model.isUnlocked, isFalse); }); test('returns true when unlockedAt is set', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', unlockedAt: DateTime.now(), ); expect(model.isUnlocked, isTrue); }); }); group('isLocked', () { test('returns true when unlockedAt is null', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', ); expect(model.isLocked, isTrue); }); test('returns false when unlockedAt is set', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', unlockedAt: DateTime.now(), ); expect(model.isLocked, isFalse); }); }); group('unlock', () { test('sets unlockedAt to current time', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', ); final beforeUnlock = DateTime.now(); final unlocked = model.unlock(); final afterUnlock = DateTime.now(); expect(unlocked.unlockedAt, isNotNull); expect( unlocked.unlockedAt!.isAfter( beforeUnlock.subtract(const Duration(seconds: 1)), ), isTrue, ); expect( unlocked.unlockedAt!.isBefore( afterUnlock.add(const Duration(seconds: 1)), ), isTrue, ); }); test('sets progress to 1.0', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', progress: 0.5, ); final unlocked = model.unlock(); expect(unlocked.progress, 1.0); }); test('preserves other fields', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', iconUrl: 'https://example.com/icon.png', type: AchievementType.streak7Days, ); final unlocked = model.unlock(); expect(unlocked.id, model.id); expect(unlocked.title, model.title); expect(unlocked.description, model.description); expect(unlocked.iconUrl, model.iconUrl); expect(unlocked.type, model.type); }); }); group('updateProgress', () { test('updates progress correctly', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', progress: 0.3, ); final updated = model.updateProgress(0.7); expect(updated.progress, 0.7); }); test('clamps progress to 0.0 minimum', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', ); final updated = model.updateProgress(-0.5); expect(updated.progress, 0.0); }); test('clamps progress to 1.0 maximum', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', ); final updated = model.updateProgress(1.5); expect(updated.progress, 1.0); }); test('preserves unlockedAt when updating progress', () { final now = DateTime.now(); final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', unlockedAt: now, ); final updated = model.updateProgress(0.5); expect(updated.unlockedAt, now); }); test('preserves other fields', () { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', iconUrl: 'https://example.com/icon.png', type: AchievementType.words50Learned, ); final updated = model.updateProgress(0.6); expect(updated.id, model.id); expect(updated.title, model.title); expect(updated.description, model.description); expect(updated.iconUrl, model.iconUrl); expect(updated.type, model.type); }); }); group('category', () { test('returns correct category for first_steps achievements', () { expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.firstWordLearned, ).category, 'first_steps', ); expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.firstTestCompleted, ).category, 'first_steps', ); expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.firstPackCompleted, ).category, 'first_steps', ); }); test('returns correct category for streaks achievements', () { expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.streak3Days, ).category, 'streaks', ); expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.streak100Days, ).category, 'streaks', ); }); test('returns correct category for words_mastery achievements', () { expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.words10Learned, ).category, 'words_mastery', ); expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.words1000Learned, ).category, 'words_mastery', ); }); test('returns correct category for performance achievements', () { expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.perfectTestScore, ).category, 'performance', ); expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.speedLearner, ).category, 'performance', ); }); test('returns correct category for dedication achievements', () { expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.dedicatedLearner, ).category, 'dedication', ); }); test('returns correct category for time_based achievements', () { expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.nightOwl, ).category, 'time_based', ); expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.earlyBird, ).category, 'time_based', ); }); test('returns correct category for special achievements', () { expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.consistentLearner, ).category, 'special', ); expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', type: AchievementType.languageMaster, ).category, 'special', ); }); }); group('edge cases', () { test('handles empty strings', () { final model = AchievementModel(id: '', title: '', description: ''); expect(model.id, ''); expect(model.title, ''); expect(model.description, ''); }); test('handles very long strings', () { final longString = 'a' * 1000; final model = AchievementModel( id: longString, title: longString, description: longString, ); expect(model.id, longString); expect(model.title, longString); expect(model.description, longString); }); test('handles progress at boundaries', () { expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', progress: 0.0, ).progress, 0.0, ); expect( AchievementModel( id: 'test', title: 'Test', description: 'Test', progress: 1.0, ).progress, 1.0, ); }); test('handles all achievement types', () { for (final type in AchievementType.values) { final model = AchievementModel( id: 'test', title: 'Test', description: 'Test', type: type, ); expect(model.type, type); expect(model.category, isNotEmpty); } }); }); }); }