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 = {}; // 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 = {}; // 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 = {}; // 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'); }); }); }