590 lines
15 KiB
Dart
590 lines
15 KiB
Dart
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);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|