Files
classroomio/apps/dashboard/src/lib/features/course/utils/exercise-progression-utils.test.ts
T

142 lines
4.1 KiB
TypeScript
Raw Normal View History

import { describe, expect, it } from 'vitest';
import type { Question } from '$features/course/types';
import type { SubmissionListItem } from '$features/course/utils/types';
import {
getManualGradedExerciseQuestions,
getTotalPossibleExercisePoints,
groupSubmissionsByStudentAndAttempt,
shouldCompleteExerciseFromSubmission
} from './exercise-progression-utils';
function makeQuestion(overrides: Partial<Question>): Question {
return {
id: overrides.id ?? 1,
title: overrides.title ?? 'Question',
points: overrides.points ?? 1,
questionTypeId: overrides.questionTypeId ?? 1,
questionType: overrides.questionType,
options: [],
...overrides
} as Question;
}
function makeSubmission(overrides: Partial<SubmissionListItem> & { id: string }): SubmissionListItem {
return {
id: overrides.id,
statusId: overrides.statusId ?? 1,
gradingState: overrides.gradingState,
total: overrides.total ?? 0,
createdAt: overrides.createdAt ?? '2026-01-01T00:00:00.000Z',
groupmember: overrides.groupmember ?? {
profile: {
id: 'student-1',
fullname: 'Student One',
avatarUrl: ''
}
},
answers: overrides.answers ?? []
} as unknown as SubmissionListItem;
}
describe('exercise progression helpers', () => {
it('detects manual-graded questions from question type ids', () => {
const questions = [
makeQuestion({ id: 1, questionTypeId: 1 }),
makeQuestion({ id: 2, questionTypeId: 3 }),
makeQuestion({ id: 3, questionTypeId: 8 })
];
expect(getManualGradedExerciseQuestions(questions).map((question) => question.id)).toEqual([2, 3]);
});
it('calculates total possible points from active questions', () => {
const questions = [
makeQuestion({ id: 1, points: 3 }),
makeQuestion({ id: 2, points: 2 }),
makeQuestion({ id: 3, points: 20, deletedAt: '2026-01-01' })
];
expect(getTotalPossibleExercisePoints(questions)).toBe(5);
});
it('decides when a submission completes a passed-policy exercise', () => {
const passingSubmission = makeSubmission({
id: 'passing',
gradingState: 'completed',
total: 8
});
const failingSubmission = makeSubmission({
id: 'failing',
gradingState: 'completed',
total: 6
});
const awaitingManualSubmission = makeSubmission({
id: 'manual',
gradingState: 'awaiting_manual',
total: 10
});
expect(
shouldCompleteExerciseFromSubmission({
completionPolicy: 'passed',
passThreshold: 70,
totalPossiblePoints: 10,
submission: passingSubmission
})
).toBe(true);
expect(
shouldCompleteExerciseFromSubmission({
completionPolicy: 'passed',
passThreshold: 70,
totalPossiblePoints: 10,
submission: failingSubmission
})
).toBe(false);
expect(
shouldCompleteExerciseFromSubmission({
completionPolicy: 'passed',
passThreshold: 70,
totalPossiblePoints: 10,
submission: awaitingManualSubmission
})
).toBe(false);
});
it('treats submitted-policy submissions as complete regardless of grading state', () => {
const submission = makeSubmission({
id: 'submitted',
gradingState: 'awaiting_manual',
total: 0
});
expect(
shouldCompleteExerciseFromSubmission({
completionPolicy: 'submitted',
passThreshold: 100,
totalPossiblePoints: 0,
submission
})
).toBe(true);
});
it('groups submissions by student and orders attempts by submission date', () => {
const olderAttempt = makeSubmission({
id: 'attempt-1',
total: 4,
createdAt: '2026-01-01T00:00:00.000Z'
});
const newerAttempt = makeSubmission({
id: 'attempt-2',
total: 9,
createdAt: '2026-01-02T00:00:00.000Z'
});
const groups = groupSubmissionsByStudentAndAttempt([newerAttempt, olderAttempt]);
expect(groups).toHaveLength(1);
expect(groups[0].attempts.map((attempt) => attempt.submission.id)).toEqual(['attempt-1', 'attempt-2']);
expect(groups[0].attempts.map((attempt) => attempt.attemptNumber)).toEqual([1, 2]);
});
});