You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
5.9 KiB
204 lines
5.9 KiB
import 'package:test/test.dart';
|
|
import 'package:practice_engine/models/deck.dart';
|
|
import 'package:practice_engine/models/deck_config.dart';
|
|
import 'package:practice_engine/models/question.dart';
|
|
import 'package:practice_engine/logic/attempt_service.dart';
|
|
import 'package:practice_engine/logic/deck_service.dart';
|
|
|
|
void main() {
|
|
group('Attempt Flow', () {
|
|
late Deck deck;
|
|
late AttemptService attemptService;
|
|
|
|
setUp(() {
|
|
final config = const DeckConfig(
|
|
defaultAttemptSize: 5,
|
|
requiredConsecutiveCorrect: 3,
|
|
);
|
|
|
|
final questions = List.generate(10, (i) {
|
|
return Question(
|
|
id: 'q$i',
|
|
prompt: 'Question $i',
|
|
answers: ['A', 'B', 'C'],
|
|
correctAnswerIndices: [0],
|
|
priorityPoints: i,
|
|
);
|
|
});
|
|
|
|
deck = Deck(
|
|
id: 'deck1',
|
|
title: 'Test Deck',
|
|
description: 'Test',
|
|
questions: questions,
|
|
config: config,
|
|
);
|
|
|
|
attemptService = AttemptService(seed: 42);
|
|
});
|
|
|
|
test('createAttempt selects correct number of questions', () {
|
|
final attempt = attemptService.createAttempt(deck: deck);
|
|
|
|
expect(attempt.questions.length, equals(deck.config.defaultAttemptSize));
|
|
});
|
|
|
|
test('createAttempt uses custom attempt size', () {
|
|
final attempt = attemptService.createAttempt(
|
|
deck: deck,
|
|
attemptSize: 3,
|
|
);
|
|
|
|
expect(attempt.questions.length, equals(3));
|
|
});
|
|
|
|
test('createAttempt has no duplicate questions', () {
|
|
final attempt = attemptService.createAttempt(deck: deck);
|
|
|
|
final ids = attempt.questions.map((q) => q.id).toList();
|
|
final uniqueIds = ids.toSet();
|
|
|
|
expect(uniqueIds.length, equals(ids.length));
|
|
});
|
|
|
|
test('processAttempt updates deck with results', () {
|
|
final attempt = attemptService.createAttempt(deck: deck);
|
|
final answers = <String, int>{};
|
|
|
|
// Answer all questions correctly
|
|
for (final question in attempt.questions) {
|
|
answers[question.id] = question.correctIndices.first;
|
|
}
|
|
|
|
final result = attemptService.processAttempt(
|
|
deck: deck,
|
|
attempt: attempt,
|
|
answers: answers,
|
|
);
|
|
|
|
expect(result.updatedDeck.currentAttemptIndex,
|
|
equals(deck.currentAttemptIndex + 1));
|
|
});
|
|
|
|
test('processAttempt calculates correct percentage', () {
|
|
final attempt = attemptService.createAttempt(
|
|
deck: deck,
|
|
attemptSize: 3,
|
|
);
|
|
final answers = <String, int>{};
|
|
|
|
// Answer 2 out of 3 correctly
|
|
answers[attempt.questions[0].id] =
|
|
attempt.questions[0].correctIndices.first;
|
|
answers[attempt.questions[1].id] =
|
|
attempt.questions[1].correctIndices.first;
|
|
answers[attempt.questions[2].id] = 999; // Wrong answer
|
|
|
|
final result = attemptService.processAttempt(
|
|
deck: deck,
|
|
attempt: attempt,
|
|
answers: answers,
|
|
);
|
|
|
|
expect(result.result.percentageCorrect, closeTo(66.67, 0.01));
|
|
expect(result.result.correctCount, equals(2));
|
|
});
|
|
|
|
test('processAttempt tracks incorrect questions', () {
|
|
final attempt = attemptService.createAttempt(
|
|
deck: deck,
|
|
attemptSize: 3,
|
|
);
|
|
final answers = <String, int>{};
|
|
|
|
// Answer first correctly, rest incorrectly
|
|
answers[attempt.questions[0].id] =
|
|
attempt.questions[0].correctIndices.first;
|
|
answers[attempt.questions[1].id] = 999;
|
|
answers[attempt.questions[2].id] = 999;
|
|
|
|
final result = attemptService.processAttempt(
|
|
deck: deck,
|
|
attempt: attempt,
|
|
answers: answers,
|
|
);
|
|
|
|
expect(result.result.incorrectQuestions.length, equals(2));
|
|
});
|
|
|
|
test('processAttempt updates question streaks correctly', () {
|
|
final question = Question(
|
|
id: 'q1',
|
|
prompt: 'Test',
|
|
answers: ['A', 'B'],
|
|
correctAnswerIndices: [0],
|
|
consecutiveCorrect: 2,
|
|
);
|
|
|
|
final testDeck = deck.copyWith(questions: [question]);
|
|
final attempt = attemptService.createAttempt(
|
|
deck: testDeck,
|
|
attemptSize: 1,
|
|
);
|
|
|
|
final answers = {question.id: question.correctIndices.first};
|
|
|
|
final result = attemptService.processAttempt(
|
|
deck: testDeck,
|
|
attempt: attempt,
|
|
answers: answers,
|
|
);
|
|
|
|
final updated = result.updatedDeck.questions.first;
|
|
expect(updated.consecutiveCorrect, equals(3));
|
|
expect(updated.isKnown, equals(true));
|
|
});
|
|
|
|
test('processAttempt preserves manual mark as known when using current deck state', () {
|
|
// One question, not known, no streak
|
|
final question = Question(
|
|
id: 'q1',
|
|
prompt: 'Test',
|
|
answers: ['A', 'B'],
|
|
correctAnswerIndices: [0],
|
|
consecutiveCorrect: 0,
|
|
isKnown: false,
|
|
);
|
|
|
|
final testDeck = Deck(
|
|
id: 'deck1',
|
|
title: 'Test',
|
|
description: 'Test',
|
|
questions: [question],
|
|
config: const DeckConfig(requiredConsecutiveCorrect: 3),
|
|
);
|
|
|
|
final attempt = attemptService.createAttempt(
|
|
deck: testDeck,
|
|
attemptSize: 1,
|
|
);
|
|
expect(attempt.questions.single.isKnown, isFalse);
|
|
|
|
// User marks as known during the attempt (as in the app)
|
|
final deckWithManualKnown = DeckService.markQuestionAsKnown(
|
|
deck: testDeck,
|
|
questionId: question.id,
|
|
);
|
|
expect(deckWithManualKnown.questions.single.isKnown, isTrue);
|
|
|
|
// Complete attempt with correct answer, passing deck that has manual known
|
|
final answers = {question.id: question.correctIndices.first};
|
|
final result = attemptService.processAttempt(
|
|
deck: deckWithManualKnown,
|
|
attempt: attempt,
|
|
answers: answers,
|
|
);
|
|
|
|
// Manual "known" must be preserved in the result
|
|
final updated = result.updatedDeck.questions.first;
|
|
expect(updated.isKnown, isTrue, reason: 'Manual mark as known must count towards known after attempt');
|
|
});
|
|
});
|
|
}
|
|
|