feat(backend): Write Unit Tests for Statistics Models
Task ID: BACKEND-007 Priority: medium Changes: Completed by: AI Agent Duration: 110754ms
This commit is contained in:
parent
840e453255
commit
4a468bd0c3
6 changed files with 1649 additions and 4 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"component": "backend",
|
||||
"current_task_id": "BACKEND-006",
|
||||
"iteration_count": 4,
|
||||
"current_task_id": "BACKEND-007",
|
||||
"iteration_count": 5,
|
||||
"max_iterations": 10,
|
||||
"started_at": "2025-11-21T01:06:15.789515+00:00",
|
||||
"last_commit": null,
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@
|
|||
"id": "BACKEND-007",
|
||||
"title": "Write Unit Tests for Statistics Models",
|
||||
"priority": "medium",
|
||||
"status": "pending",
|
||||
"status": "in_progress",
|
||||
"estimated_hours": 3.0,
|
||||
"description": "Write comprehensive unit tests for all statistics-related models (PackProgressModel, AchievementModel, StudySessionModel) including toDto/fromDto conversions, serialization, and edge cases.",
|
||||
"acceptance_criteria": [
|
||||
|
|
|
|||
590
mnemo_cards_backend/test/models/achievement_model_test.dart
Normal file
590
mnemo_cards_backend/test/models/achievement_model_test.dart
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
415
mnemo_cards_backend/test/models/pack_progress_model_test.dart
Normal file
415
mnemo_cards_backend/test/models/pack_progress_model_test.dart
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
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('PackProgressModel', () {
|
||||
group('constructor', () {
|
||||
test('creates model with default values', () {
|
||||
final model = PackProgressModel();
|
||||
|
||||
expect(model.packId, '');
|
||||
expect(model.totalCards, 0);
|
||||
expect(model.learnedCards, 0);
|
||||
expect(model.studyTimeMinutes, 0);
|
||||
expect(model.lastStudyDate, isNull);
|
||||
expect(model.firstStudyDate, isNull);
|
||||
expect(model.cardAttempts, isEmpty);
|
||||
expect(model.averageAccuracy, 0.0);
|
||||
});
|
||||
|
||||
test('creates model with all parameters', () {
|
||||
final now = DateTime.now();
|
||||
final yesterday = now.subtract(const Duration(days: 1));
|
||||
final cardAttempts = {'card1': 3, 'card2': 5};
|
||||
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
totalCards: 20,
|
||||
learnedCards: 15,
|
||||
studyTimeMinutes: 120,
|
||||
lastStudyDate: now,
|
||||
firstStudyDate: yesterday,
|
||||
cardAttempts: cardAttempts,
|
||||
averageAccuracy: 0.85,
|
||||
);
|
||||
|
||||
expect(model.packId, 'test_pack');
|
||||
expect(model.totalCards, 20);
|
||||
expect(model.learnedCards, 15);
|
||||
expect(model.studyTimeMinutes, 120);
|
||||
expect(model.lastStudyDate, now);
|
||||
expect(model.firstStudyDate, yesterday);
|
||||
expect(model.cardAttempts, cardAttempts);
|
||||
expect(model.averageAccuracy, 0.85);
|
||||
});
|
||||
|
||||
test('creates model with empty cardAttempts map', () {
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
cardAttempts: const {},
|
||||
);
|
||||
|
||||
expect(model.cardAttempts, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('toDto', () {
|
||||
test('converts model to DTO correctly', () {
|
||||
final now = DateTime.now();
|
||||
final cardAttempts = {'card1': 2, 'card2': 4};
|
||||
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
totalCards: 10,
|
||||
learnedCards: 7,
|
||||
studyTimeMinutes: 60,
|
||||
lastStudyDate: now,
|
||||
firstStudyDate: now,
|
||||
cardAttempts: cardAttempts,
|
||||
averageAccuracy: 0.75,
|
||||
);
|
||||
|
||||
final dto = model.toDto();
|
||||
|
||||
expect(dto.packId, 'test_pack');
|
||||
expect(dto.totalCards, 10);
|
||||
expect(dto.learnedCards, 7);
|
||||
expect(dto.studyTimeMinutes, 60);
|
||||
expect(dto.lastStudyDate, now);
|
||||
expect(dto.firstStudyDate, now);
|
||||
expect(dto.cardAttempts, cardAttempts);
|
||||
expect(dto.averageAccuracy, 0.75);
|
||||
});
|
||||
|
||||
test('converts model with null dates to DTO', () {
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
totalCards: 10,
|
||||
);
|
||||
|
||||
final dto = model.toDto();
|
||||
|
||||
expect(dto.lastStudyDate, isNull);
|
||||
expect(dto.firstStudyDate, isNull);
|
||||
});
|
||||
|
||||
test('creates new map instance in DTO', () {
|
||||
final cardAttempts = {'card1': 1};
|
||||
final model = PackProgressModel(cardAttempts: cardAttempts);
|
||||
|
||||
final dto = model.toDto();
|
||||
|
||||
expect(dto.cardAttempts, equals(cardAttempts));
|
||||
expect(dto.cardAttempts, isNot(same(cardAttempts)));
|
||||
});
|
||||
});
|
||||
|
||||
group('fromDto', () {
|
||||
test('creates model from DTO correctly', () {
|
||||
final now = DateTime.now();
|
||||
final cardAttempts = {'card1': 3, 'card2': 5};
|
||||
|
||||
final dto = PackProgressDto(
|
||||
packId: 'test_pack',
|
||||
totalCards: 20,
|
||||
learnedCards: 15,
|
||||
studyTimeMinutes: 120,
|
||||
lastStudyDate: now,
|
||||
firstStudyDate: now,
|
||||
cardAttempts: cardAttempts,
|
||||
averageAccuracy: 0.85,
|
||||
);
|
||||
|
||||
final model = PackProgressModel.fromDto(dto);
|
||||
|
||||
expect(model.packId, 'test_pack');
|
||||
expect(model.totalCards, 20);
|
||||
expect(model.learnedCards, 15);
|
||||
expect(model.studyTimeMinutes, 120);
|
||||
expect(model.lastStudyDate, now);
|
||||
expect(model.firstStudyDate, now);
|
||||
expect(model.cardAttempts, cardAttempts);
|
||||
expect(model.averageAccuracy, 0.85);
|
||||
});
|
||||
|
||||
test('creates model from DTO with null dates', () {
|
||||
final dto = PackProgressDto(
|
||||
packId: 'test_pack',
|
||||
totalCards: 10,
|
||||
);
|
||||
|
||||
final model = PackProgressModel.fromDto(dto);
|
||||
|
||||
expect(model.lastStudyDate, isNull);
|
||||
expect(model.firstStudyDate, isNull);
|
||||
});
|
||||
|
||||
test('creates new map instance from DTO', () {
|
||||
final cardAttempts = {'card1': 1};
|
||||
final dto = PackProgressDto(
|
||||
packId: 'test_pack',
|
||||
totalCards: 10,
|
||||
cardAttempts: cardAttempts,
|
||||
);
|
||||
|
||||
final model = PackProgressModel.fromDto(dto);
|
||||
|
||||
expect(model.cardAttempts, equals(cardAttempts));
|
||||
expect(model.cardAttempts, isNot(same(cardAttempts)));
|
||||
});
|
||||
});
|
||||
|
||||
group('toDto and fromDto roundtrip', () {
|
||||
test('preserves all data through conversion', () {
|
||||
final now = DateTime.now();
|
||||
final cardAttempts = {'card1': 2, 'card2': 4, 'card3': 1};
|
||||
|
||||
final original = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
totalCards: 30,
|
||||
learnedCards: 20,
|
||||
studyTimeMinutes: 180,
|
||||
lastStudyDate: now,
|
||||
firstStudyDate: now.subtract(const Duration(days: 5)),
|
||||
cardAttempts: cardAttempts,
|
||||
averageAccuracy: 0.92,
|
||||
);
|
||||
|
||||
final dto = original.toDto();
|
||||
final restored = PackProgressModel.fromDto(dto);
|
||||
|
||||
expect(restored.packId, original.packId);
|
||||
expect(restored.totalCards, original.totalCards);
|
||||
expect(restored.learnedCards, original.learnedCards);
|
||||
expect(restored.studyTimeMinutes, original.studyTimeMinutes);
|
||||
expect(restored.lastStudyDate, original.lastStudyDate);
|
||||
expect(restored.firstStudyDate, original.firstStudyDate);
|
||||
expect(restored.cardAttempts, original.cardAttempts);
|
||||
expect(restored.averageAccuracy, original.averageAccuracy);
|
||||
});
|
||||
});
|
||||
|
||||
group('updateCardProgress', () {
|
||||
test('updates card attempts correctly', () {
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
totalCards: 10,
|
||||
cardAttempts: {'card1': 2},
|
||||
);
|
||||
|
||||
final updated = model.updateCardProgress(
|
||||
cardId: 'card1',
|
||||
wasCorrect: true,
|
||||
additionalTimeMinutes: 5,
|
||||
);
|
||||
|
||||
expect(updated.cardAttempts['card1'], 3);
|
||||
});
|
||||
|
||||
test('adds new card to attempts map', () {
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
totalCards: 10,
|
||||
);
|
||||
|
||||
final updated = model.updateCardProgress(
|
||||
cardId: 'new_card',
|
||||
wasCorrect: true,
|
||||
additionalTimeMinutes: 5,
|
||||
);
|
||||
|
||||
expect(updated.cardAttempts['new_card'], 1);
|
||||
});
|
||||
|
||||
test('updates study time correctly', () {
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
studyTimeMinutes: 10,
|
||||
);
|
||||
|
||||
final updated = model.updateCardProgress(
|
||||
cardId: 'card1',
|
||||
wasCorrect: true,
|
||||
additionalTimeMinutes: 5,
|
||||
);
|
||||
|
||||
expect(updated.studyTimeMinutes, 15);
|
||||
});
|
||||
|
||||
test('sets lastStudyDate to current time', () {
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
);
|
||||
|
||||
final beforeUpdate = DateTime.now();
|
||||
final updated = model.updateCardProgress(
|
||||
cardId: 'card1',
|
||||
wasCorrect: true,
|
||||
additionalTimeMinutes: 5,
|
||||
);
|
||||
final afterUpdate = DateTime.now();
|
||||
|
||||
expect(updated.lastStudyDate, isNotNull);
|
||||
expect(
|
||||
updated.lastStudyDate!.isAfter(beforeUpdate.subtract(const Duration(seconds: 1))),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
updated.lastStudyDate!.isBefore(afterUpdate.add(const Duration(seconds: 1))),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('sets firstStudyDate if null', () {
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
);
|
||||
|
||||
final updated = model.updateCardProgress(
|
||||
cardId: 'card1',
|
||||
wasCorrect: true,
|
||||
additionalTimeMinutes: 5,
|
||||
);
|
||||
|
||||
expect(updated.firstStudyDate, isNotNull);
|
||||
});
|
||||
|
||||
test('preserves firstStudyDate if already set', () {
|
||||
final originalDate = DateTime(2024, 1, 1);
|
||||
final model = PackProgressModel(
|
||||
packId: 'test_pack',
|
||||
firstStudyDate: originalDate,
|
||||
);
|
||||
|
||||
final updated = model.updateCardProgress(
|
||||
cardId: 'card1',
|
||||
wasCorrect: true,
|
||||
additionalTimeMinutes: 5,
|
||||
);
|
||||
|
||||
expect(updated.firstStudyDate, originalDate);
|
||||
});
|
||||
});
|
||||
|
||||
group('hasStarted', () {
|
||||
test('returns false for new pack', () {
|
||||
final model = PackProgressModel();
|
||||
|
||||
expect(model.hasStarted, isFalse);
|
||||
});
|
||||
|
||||
test('returns true when learnedCards > 0', () {
|
||||
final model = PackProgressModel(learnedCards: 1);
|
||||
|
||||
expect(model.hasStarted, isTrue);
|
||||
});
|
||||
|
||||
test('returns true when studyTimeMinutes > 0', () {
|
||||
final model = PackProgressModel(studyTimeMinutes: 1);
|
||||
|
||||
expect(model.hasStarted, isTrue);
|
||||
});
|
||||
|
||||
test('returns true when both learnedCards and studyTimeMinutes > 0', () {
|
||||
final model = PackProgressModel(
|
||||
learnedCards: 5,
|
||||
studyTimeMinutes: 10,
|
||||
);
|
||||
|
||||
expect(model.hasStarted, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('progress', () {
|
||||
test('returns 0.0 when totalCards is 0', () {
|
||||
final model = PackProgressModel();
|
||||
|
||||
expect(model.progress, 0.0);
|
||||
});
|
||||
|
||||
test('returns 0.0 when learnedCards is 0', () {
|
||||
final model = PackProgressModel(totalCards: 10);
|
||||
|
||||
expect(model.progress, 0.0);
|
||||
});
|
||||
|
||||
test('calculates progress correctly', () {
|
||||
final model = PackProgressModel(
|
||||
totalCards: 10,
|
||||
learnedCards: 5,
|
||||
);
|
||||
|
||||
expect(model.progress, 0.5);
|
||||
});
|
||||
|
||||
test('calculates progress for partial completion', () {
|
||||
final model = PackProgressModel(
|
||||
totalCards: 100,
|
||||
learnedCards: 33,
|
||||
);
|
||||
|
||||
expect(model.progress, 0.33);
|
||||
});
|
||||
|
||||
test('returns 1.0 when all cards learned', () {
|
||||
final model = PackProgressModel(
|
||||
totalCards: 10,
|
||||
learnedCards: 10,
|
||||
);
|
||||
|
||||
expect(model.progress, 1.0);
|
||||
});
|
||||
|
||||
test('handles learnedCards exceeding totalCards', () {
|
||||
final model = PackProgressModel(
|
||||
totalCards: 10,
|
||||
learnedCards: 15,
|
||||
);
|
||||
|
||||
expect(model.progress, 1.5);
|
||||
});
|
||||
});
|
||||
|
||||
group('edge cases', () {
|
||||
test('handles empty cardAttempts map', () {
|
||||
final model = PackProgressModel(cardAttempts: const {});
|
||||
|
||||
expect(model.cardAttempts, isEmpty);
|
||||
expect(model.toDto().cardAttempts, isEmpty);
|
||||
});
|
||||
|
||||
test('handles large cardAttempts map', () {
|
||||
final cardAttempts = Map.fromEntries(
|
||||
List.generate(100, (i) => MapEntry('card$i', i)),
|
||||
);
|
||||
|
||||
final model = PackProgressModel(cardAttempts: cardAttempts);
|
||||
|
||||
expect(model.cardAttempts.length, 100);
|
||||
expect(model.toDto().cardAttempts.length, 100);
|
||||
});
|
||||
|
||||
test('handles zero averageAccuracy', () {
|
||||
final model = PackProgressModel(averageAccuracy: 0.0);
|
||||
|
||||
expect(model.averageAccuracy, 0.0);
|
||||
expect(model.toDto().averageAccuracy, 0.0);
|
||||
});
|
||||
|
||||
test('handles maximum averageAccuracy', () {
|
||||
final model = PackProgressModel(averageAccuracy: 1.0);
|
||||
|
||||
expect(model.averageAccuracy, 1.0);
|
||||
expect(model.toDto().averageAccuracy, 1.0);
|
||||
});
|
||||
|
||||
test('handles very large studyTimeMinutes', () {
|
||||
final model = PackProgressModel(studyTimeMinutes: 999999);
|
||||
|
||||
expect(model.studyTimeMinutes, 999999);
|
||||
expect(model.toDto().studyTimeMinutes, 999999);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
640
mnemo_cards_backend/test/models/study_session_model_test.dart
Normal file
640
mnemo_cards_backend/test/models/study_session_model_test.dart
Normal file
|
|
@ -0,0 +1,640 @@
|
|||
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('StudySessionModel', () {
|
||||
group('constructor', () {
|
||||
test('creates model with required parameters', () {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
);
|
||||
|
||||
expect(model.userId, 1);
|
||||
expect(model.startTime, startTime);
|
||||
expect(model.sessionId, isNull);
|
||||
expect(model.endTime, isNull);
|
||||
expect(model.wordsLearned, 0);
|
||||
expect(model.testsCompleted, 0);
|
||||
expect(model.accuracy, 0.0);
|
||||
expect(model.packId, isNull);
|
||||
expect(model.testId, isNull);
|
||||
});
|
||||
|
||||
test('creates model with all parameters', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 30));
|
||||
|
||||
final model = StudySessionModel(
|
||||
sessionId: 'session_123',
|
||||
userId: 42,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 10,
|
||||
testsCompleted: 2,
|
||||
accuracy: 0.85,
|
||||
packId: 'pack_1',
|
||||
testId: 'test_1',
|
||||
);
|
||||
|
||||
expect(model.sessionId, 'session_123');
|
||||
expect(model.userId, 42);
|
||||
expect(model.startTime, startTime);
|
||||
expect(model.endTime, endTime);
|
||||
expect(model.wordsLearned, 10);
|
||||
expect(model.testsCompleted, 2);
|
||||
expect(model.accuracy, 0.85);
|
||||
expect(model.packId, 'pack_1');
|
||||
expect(model.testId, 'test_1');
|
||||
});
|
||||
|
||||
test('creates model with default values', () {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
);
|
||||
|
||||
expect(model.wordsLearned, 0);
|
||||
expect(model.testsCompleted, 0);
|
||||
expect(model.accuracy, 0.0);
|
||||
});
|
||||
});
|
||||
|
||||
group('toDto', () {
|
||||
test('converts model to DTO correctly', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 30));
|
||||
|
||||
final model = StudySessionModel(
|
||||
sessionId: 'session_123',
|
||||
userId: 42,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 10,
|
||||
testsCompleted: 2,
|
||||
accuracy: 0.85,
|
||||
packId: 'pack_1',
|
||||
testId: 'test_1',
|
||||
);
|
||||
|
||||
final dto = model.toDto();
|
||||
|
||||
expect(dto.sessionId, 'session_123');
|
||||
expect(dto.startTime, startTime);
|
||||
expect(dto.endTime, endTime);
|
||||
expect(dto.wordsLearned, 10);
|
||||
expect(dto.testsCompleted, 2);
|
||||
expect(dto.accuracy, 0.85);
|
||||
expect(dto.packId, 'pack_1');
|
||||
expect(dto.testId, 'test_1');
|
||||
});
|
||||
|
||||
test('converts model with null values to DTO', () {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
);
|
||||
|
||||
final dto = model.toDto();
|
||||
|
||||
expect(dto.sessionId, isNull);
|
||||
expect(dto.endTime, isNull);
|
||||
expect(dto.packId, isNull);
|
||||
expect(dto.testId, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('fromDto', () {
|
||||
test('creates model from DTO correctly', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 30));
|
||||
|
||||
final dto = StudySessionDto(
|
||||
sessionId: 'session_123',
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 10,
|
||||
testsCompleted: 2,
|
||||
accuracy: 0.85,
|
||||
packId: 'pack_1',
|
||||
testId: 'test_1',
|
||||
);
|
||||
|
||||
final model = StudySessionModel.fromDto(dto, 42);
|
||||
|
||||
expect(model.sessionId, 'session_123');
|
||||
expect(model.userId, 42);
|
||||
expect(model.startTime, startTime);
|
||||
expect(model.endTime, endTime);
|
||||
expect(model.wordsLearned, 10);
|
||||
expect(model.testsCompleted, 2);
|
||||
expect(model.accuracy, 0.85);
|
||||
expect(model.packId, 'pack_1');
|
||||
expect(model.testId, 'test_1');
|
||||
});
|
||||
|
||||
test('creates model from DTO with null values', () {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
final dto = StudySessionDto(
|
||||
startTime: startTime,
|
||||
);
|
||||
|
||||
final model = StudySessionModel.fromDto(dto, 1);
|
||||
|
||||
expect(model.sessionId, isNull);
|
||||
expect(model.endTime, isNull);
|
||||
expect(model.packId, isNull);
|
||||
expect(model.testId, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('toDto and fromDto roundtrip', () {
|
||||
test('preserves all data through conversion', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 45));
|
||||
|
||||
final original = StudySessionModel(
|
||||
sessionId: 'session_456',
|
||||
userId: 100,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 25,
|
||||
testsCompleted: 3,
|
||||
accuracy: 0.92,
|
||||
packId: 'pack_2',
|
||||
testId: 'test_2',
|
||||
);
|
||||
|
||||
final dto = original.toDto();
|
||||
final restored = StudySessionModel.fromDto(dto, original.userId);
|
||||
|
||||
expect(restored.sessionId, original.sessionId);
|
||||
expect(restored.userId, original.userId);
|
||||
expect(restored.startTime, original.startTime);
|
||||
expect(restored.endTime, original.endTime);
|
||||
expect(restored.wordsLearned, original.wordsLearned);
|
||||
expect(restored.testsCompleted, original.testsCompleted);
|
||||
expect(restored.accuracy, original.accuracy);
|
||||
expect(restored.packId, original.packId);
|
||||
expect(restored.testId, original.testId);
|
||||
});
|
||||
});
|
||||
|
||||
group('isActive', () {
|
||||
test('returns true when endTime is null', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(model.isActive, isTrue);
|
||||
});
|
||||
|
||||
test('returns false when endTime is set', () {
|
||||
final startTime = DateTime.now();
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: startTime.add(const Duration(minutes: 30)),
|
||||
);
|
||||
|
||||
expect(model.isActive, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('duration', () {
|
||||
test('calculates duration correctly when ended', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 30));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
);
|
||||
|
||||
expect(model.duration, const Duration(minutes: 30));
|
||||
});
|
||||
|
||||
test('calculates duration using current time when active', () {
|
||||
final startTime = DateTime.now().subtract(const Duration(minutes: 15));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
);
|
||||
|
||||
final duration = model.duration;
|
||||
expect(duration.inMinutes, greaterThanOrEqualTo(14));
|
||||
expect(duration.inMinutes, lessThanOrEqualTo(16));
|
||||
});
|
||||
});
|
||||
|
||||
group('durationMinutes', () {
|
||||
test('returns duration in minutes correctly', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 45));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
);
|
||||
|
||||
expect(model.durationMinutes, 45);
|
||||
});
|
||||
|
||||
test('handles zero duration', () {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: startTime,
|
||||
);
|
||||
|
||||
expect(model.durationMinutes, 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('sessionType', () {
|
||||
test('returns Test Session when testId is set', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
testId: 'test_1',
|
||||
);
|
||||
|
||||
expect(model.sessionType, 'Test Session');
|
||||
});
|
||||
|
||||
test('returns Pack Study when packId is set', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
packId: 'pack_1',
|
||||
);
|
||||
|
||||
expect(model.sessionType, 'Pack Study');
|
||||
});
|
||||
|
||||
test('returns General Study when neither is set', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(model.sessionType, 'General Study');
|
||||
});
|
||||
|
||||
test('prioritizes testId over packId', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
packId: 'pack_1',
|
||||
testId: 'test_1',
|
||||
);
|
||||
|
||||
expect(model.sessionType, 'Test Session');
|
||||
});
|
||||
});
|
||||
|
||||
group('productivityScore', () {
|
||||
test('calculates productivity score correctly', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 30));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 15,
|
||||
);
|
||||
|
||||
expect(model.productivityScore, 0.5); // 15 words / 30 minutes
|
||||
});
|
||||
|
||||
test('returns 0.0 when duration is 0', () {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: startTime,
|
||||
wordsLearned: 10,
|
||||
);
|
||||
|
||||
expect(model.productivityScore, 0.0);
|
||||
});
|
||||
|
||||
test('handles active session', () {
|
||||
final startTime = DateTime.now().subtract(const Duration(minutes: 10));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
wordsLearned: 5,
|
||||
);
|
||||
|
||||
final score = model.productivityScore;
|
||||
expect(score, greaterThan(0));
|
||||
expect(score, lessThanOrEqualTo(1.0));
|
||||
});
|
||||
});
|
||||
|
||||
group('isProductive', () {
|
||||
test('returns true for productive session', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 10));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 10,
|
||||
accuracy: 0.8,
|
||||
);
|
||||
|
||||
expect(model.isProductive, isTrue);
|
||||
});
|
||||
|
||||
test('returns false when wordsLearned < 5', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 10));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 3,
|
||||
accuracy: 0.8,
|
||||
);
|
||||
|
||||
expect(model.isProductive, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when accuracy < 0.7', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 10));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 10,
|
||||
accuracy: 0.6,
|
||||
);
|
||||
|
||||
expect(model.isProductive, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when duration < 5 minutes', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 3));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
wordsLearned: 10,
|
||||
accuracy: 0.8,
|
||||
);
|
||||
|
||||
expect(model.isProductive, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('end', () {
|
||||
test('sets endTime to current time', () {
|
||||
final startTime = DateTime.now().subtract(const Duration(minutes: 10));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
);
|
||||
|
||||
final beforeEnd = DateTime.now();
|
||||
final ended = model.end(
|
||||
wordsLearned: 10,
|
||||
testsCompleted: 2,
|
||||
accuracy: 0.85,
|
||||
);
|
||||
final afterEnd = DateTime.now();
|
||||
|
||||
expect(ended.endTime, isNotNull);
|
||||
expect(
|
||||
ended.endTime!.isAfter(beforeEnd.subtract(const Duration(seconds: 1))),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
ended.endTime!.isBefore(afterEnd.add(const Duration(seconds: 1))),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('updates statistics correctly', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
);
|
||||
|
||||
final ended = model.end(
|
||||
wordsLearned: 15,
|
||||
testsCompleted: 3,
|
||||
accuracy: 0.9,
|
||||
);
|
||||
|
||||
expect(ended.wordsLearned, 15);
|
||||
expect(ended.testsCompleted, 3);
|
||||
expect(ended.accuracy, 0.9);
|
||||
});
|
||||
|
||||
test('preserves other fields', () {
|
||||
final model = StudySessionModel(
|
||||
sessionId: 'session_123',
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
packId: 'pack_1',
|
||||
testId: 'test_1',
|
||||
);
|
||||
|
||||
final ended = model.end();
|
||||
|
||||
expect(ended.sessionId, model.sessionId);
|
||||
expect(ended.userId, model.userId);
|
||||
expect(ended.startTime, model.startTime);
|
||||
expect(ended.packId, model.packId);
|
||||
expect(ended.testId, model.testId);
|
||||
});
|
||||
});
|
||||
|
||||
group('addProgress', () {
|
||||
test('adds wordsLearned correctly', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
wordsLearned: 5,
|
||||
);
|
||||
|
||||
final updated = model.addProgress(wordsLearned: 3);
|
||||
|
||||
expect(updated.wordsLearned, 8);
|
||||
});
|
||||
|
||||
test('adds testsCompleted correctly', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
testsCompleted: 1,
|
||||
);
|
||||
|
||||
final updated = model.addProgress(testsCompleted: 2);
|
||||
|
||||
expect(updated.testsCompleted, 3);
|
||||
});
|
||||
|
||||
test('updates accuracy when provided', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
accuracy: 0.5,
|
||||
);
|
||||
|
||||
final updated = model.addProgress(accuracy: 0.8);
|
||||
|
||||
expect(updated.accuracy, 0.8);
|
||||
});
|
||||
|
||||
test('preserves accuracy when not provided', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
accuracy: 0.7,
|
||||
);
|
||||
|
||||
final updated = model.addProgress(wordsLearned: 5);
|
||||
|
||||
expect(updated.accuracy, 0.7);
|
||||
});
|
||||
|
||||
test('preserves endTime', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(minutes: 30));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
);
|
||||
|
||||
final updated = model.addProgress(wordsLearned: 5);
|
||||
|
||||
expect(updated.endTime, endTime);
|
||||
});
|
||||
|
||||
test('preserves other fields', () {
|
||||
final model = StudySessionModel(
|
||||
sessionId: 'session_123',
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
packId: 'pack_1',
|
||||
testId: 'test_1',
|
||||
);
|
||||
|
||||
final updated = model.addProgress(wordsLearned: 5);
|
||||
|
||||
expect(updated.sessionId, model.sessionId);
|
||||
expect(updated.userId, model.userId);
|
||||
expect(updated.startTime, model.startTime);
|
||||
expect(updated.packId, model.packId);
|
||||
expect(updated.testId, model.testId);
|
||||
});
|
||||
});
|
||||
|
||||
group('edge cases', () {
|
||||
test('handles zero wordsLearned', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
wordsLearned: 0,
|
||||
);
|
||||
|
||||
expect(model.wordsLearned, 0);
|
||||
expect(model.toDto().wordsLearned, 0);
|
||||
});
|
||||
|
||||
test('handles zero testsCompleted', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
testsCompleted: 0,
|
||||
);
|
||||
|
||||
expect(model.testsCompleted, 0);
|
||||
expect(model.toDto().testsCompleted, 0);
|
||||
});
|
||||
|
||||
test('handles zero accuracy', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
accuracy: 0.0,
|
||||
);
|
||||
|
||||
expect(model.accuracy, 0.0);
|
||||
expect(model.toDto().accuracy, 0.0);
|
||||
});
|
||||
|
||||
test('handles maximum accuracy', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
accuracy: 1.0,
|
||||
);
|
||||
|
||||
expect(model.accuracy, 1.0);
|
||||
expect(model.toDto().accuracy, 1.0);
|
||||
});
|
||||
|
||||
test('handles very long session', () {
|
||||
final startTime = DateTime.now();
|
||||
final endTime = startTime.add(const Duration(hours: 5));
|
||||
|
||||
final model = StudySessionModel(
|
||||
userId: 1,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
);
|
||||
|
||||
expect(model.durationMinutes, 300);
|
||||
});
|
||||
|
||||
test('handles empty sessionId', () {
|
||||
final model = StudySessionModel(
|
||||
sessionId: '',
|
||||
userId: 1,
|
||||
startTime: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(model.sessionId, '');
|
||||
});
|
||||
|
||||
test('handles very large userId', () {
|
||||
final model = StudySessionModel(
|
||||
userId: 999999999,
|
||||
startTime: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(model.userId, 999999999);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ class PackProgressModel {
|
|||
required int additionalTimeMinutes,
|
||||
}) {
|
||||
final currentAttempts = cardAttempts[cardId] ?? 0;
|
||||
final newAttempts = {cardId: currentAttempts + 1, ...cardAttempts};
|
||||
final newAttempts = {...cardAttempts, cardId: currentAttempts + 1};
|
||||
|
||||
// Simple accuracy calculation - could be more sophisticated
|
||||
final totalAttempts = newAttempts.values.fold<int>(0, (sum, attempts) => sum + attempts);
|
||||
|
|
|
|||
Loading…
Reference in a new issue