Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add completion criteria to the side panel #4059

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import each from 'jest-each';
import {
floatOrIntRegex,
getCorrectAnswersIndices,
mapCorrectAnswers,
updateAnswersToQuestionType,
isImportedContent,
importedChannelLink,
secondsToHms,
getCompletionCriteriaLabels,
} from '../utils';
import router from '../router';
import { RouteNames } from '../constants';
import { AssessmentItemTypes } from 'shared/constants';
import { MasteryModelsNames } from 'shared/leUtils/MasteryModels';
import { AssessmentItemTypes, CompletionCriteriaModels } from 'shared/constants';

describe('channelEdit utils', () => {
describe('imported content', () => {
Expand Down Expand Up @@ -451,4 +455,221 @@ describe('channelEdit utils', () => {
].forEach(v => expect(floatOrIntRegex.test(v)).toBe(false));
});
});

describe(`secondsToHms`, () => {
it(`converts 0 seconds to '00:00'`, () => {
expect(secondsToHms(0)).toBe('00:00');
});

it(`converts seconds to 'mm:ss' when it's less than one hour`, () => {
expect(secondsToHms(3599)).toBe('59:59');
});

it(`converts seconds to 'hh:mm:ss' when it's exactly one hour`, () => {
expect(secondsToHms(3600)).toBe('01:00:00');
});

it(`converts seconds to 'hh:mm:ss' when it's more than one hour`, () => {
expect(secondsToHms(7323)).toBe('02:02:03');
});
});

describe(`getCompletionCriteriaLabels`, () => {
describe(`for 'reference' completion criteria`, () => {
it(`returns 'Reference material' completion label and empty duration label`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.REFERENCE,
},
},
},
})
).toEqual({
completion: 'Reference material',
duration: '-',
});
});
});

describe(`for 'time' completion criteria`, () => {
it(`returns 'When time spent is equal to duration' completion label and human-readable duration label`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.TIME,
},
},
},
suggested_duration: 3820,
})
).toEqual({
completion: 'When time spent is equal to duration',
duration: '01:03:40',
});
});
});

describe(`for 'approximate time' completion criteria`, () => {
it(`returns 'When time spent is equal to duration' completion label`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.APPROX_TIME,
},
},
},
suggested_duration: 1859,
}).completion
).toBe('When time spent is equal to duration');
});

it(`returns 'Short activity' duration label for a short activity`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.APPROX_TIME,
},
},
},
suggested_duration: 1860,
}).duration
).toBe('Short activity');
});

it(`returns 'Long activity' duration label for a long activity`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.APPROX_TIME,
},
},
},
suggested_duration: 1861,
}).duration
).toBe('Long activity');
});
});

describe(`for 'pages' completion criteria`, () => {
it(`returns 'Viewed in its entirety' completion label and empty duration label`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.PAGES,
threshold: '100%',
},
},
},
})
).toEqual({
completion: 'Viewed in its entirety',
duration: '-',
});
});
});

describe(`for 'determined by resource' completion criteria`, () => {
it(`returns 'Determined by the resource' completion label and empty duration label`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.DETERMINED_BY_RESOURCE,
},
},
},
})
).toEqual({
completion: 'Determined by the resource',
duration: '-',
});
});
});

describe(`for 'mastery' completion criteria`, () => {
it(`returns 'Goal: m out of n' completion label and empty duration label for 'm of n' mastery`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.MASTERY,
threshold: {
mastery_model: MasteryModelsNames.M_OF_N,
m: 4,
n: 5,
},
},
},
},
})
).toEqual({
completion: 'Goal: 4 out of 5',
duration: '-',
});
});

it(`returns 'Goal: 100% correct' completion label and empty duration label for 'do all' mastery`, () => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.MASTERY,
threshold: {
mastery_model: MasteryModelsNames.DO_ALL,
},
},
},
},
})
).toEqual({
completion: 'Goal: 100% correct',
duration: '-',
});
});

each([
[2, MasteryModelsNames.NUM_CORRECT_IN_A_ROW_2],
[3, MasteryModelsNames.NUM_CORRECT_IN_A_ROW_3],
[5, MasteryModelsNames.NUM_CORRECT_IN_A_ROW_5],
[10, MasteryModelsNames.NUM_CORRECT_IN_A_ROW_10],
]).it(
`returns 'Goal: %s in a row' completion label and empty duration label for '%s' mastery`,
(num, mastery_model) => {
expect(
getCompletionCriteriaLabels({
extra_fields: {
options: {
completion_criteria: {
model: CompletionCriteriaModels.MASTERY,
threshold: {
mastery_model,
},
},
},
},
})
).toEqual({
completion: `Goal: ${num} in a row`,
duration: '-',
});
}
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,21 @@
showEachActivityIcon
/>
</DetailsRow>
<DetailsRow v-if="isExercise" :label="$tr('completion')">
<span v-if="noMasteryModel" class="red--text">

<DetailsRow :label="translateMetadataString('completion')">
<span v-if="isExercise && noMasteryModel" class="red--text">
<Icon color="red" small>error</Icon>
<span class="mx-1">{{ $tr('noMasteryModelError') }}</span>
</span>
<span v-else>
{{ masteryCriteria }}
{{ completion }}
</span>
</DetailsRow>

<DetailsRow :label="translateMetadataString('duration')">
{{ duration }}
</DetailsRow>

<DetailsRow
v-if="!isTopic"
:label="translateMetadataString('category')"
Expand Down Expand Up @@ -332,11 +338,12 @@
import sortBy from 'lodash/sortBy';
import { mapActions, mapGetters } from 'vuex';
import camelCase from 'lodash/camelCase';
import { isImportedContent, importedChannelLink } from '../utils';
import { isImportedContent, importedChannelLink, getCompletionCriteriaLabels } from '../utils';
import FilePreview from '../views/files/FilePreview';
import { ContentLevel, Categories, AccessibilityCategories } from '../../shared/constants';
import AssessmentItemPreview from './AssessmentItemPreview/AssessmentItemPreview';
import ContentNodeValidator from './ContentNodeValidator';

import {
getAssessmentItemErrors,
getNodeLicenseErrors,
Expand All @@ -360,7 +367,6 @@
titleMixin,
metadataTranslationMixin,
} from 'shared/mixins';
import { MasteryModelsNames } from 'shared/leUtils/MasteryModels';
import { ContentKindsNames } from 'shared/leUtils/ContentKinds';

export default {
Expand Down Expand Up @@ -405,6 +411,12 @@
node() {
return this.getContentNode(this.nodeId);
},
completion() {
return getCompletionCriteriaLabels(this.node).completion;
},
duration() {
return getCompletionCriteriaLabels(this.node).duration;
},
files() {
return sortBy(this.getContentNodeFiles(this.nodeId), f => f.preset.order);
},
Expand Down Expand Up @@ -459,19 +471,6 @@
importedChannelName() {
return this.node.original_channel_name;
},
masteryCriteria() {
if (!this.isExercise) {
return '';
}

const masteryModel = this.node.extra_fields.mastery_model;
if (!masteryModel) {
return this.defaultText;
} else if (masteryModel === MasteryModelsNames.M_OF_N) {
return this.$tr('masteryMofN', this.node.extra_fields);
}
return this.translateConstant(masteryModel);
},
sortedTags() {
return orderBy(this.node.tags, ['count'], ['desc']);
},
Expand Down Expand Up @@ -663,9 +662,7 @@
},
$trs: {
questions: 'Questions',
masteryMofN: 'Goal: {m} out of {n}',
details: 'Details',
completion: 'Completion',
showAnswers: 'Show answers',
questionCount: '{value, number, integer} {value, plural, one {question} other {questions}}',
description: 'Description',
Expand All @@ -692,6 +689,11 @@
fileSize: 'Size',

// Validation strings
/* eslint-disable kolibri/vue-no-unused-translations */
noLearningActivityError: 'Missing learning activity',
noCompletionCriteriaError: 'Missing completion criteria',
noDurationError: 'Missing duration',
/* eslint-enable kolibri/vue-no-unused-translations */
noLicenseError: 'Missing license',
noCopyrightHolderError: 'Missing copyright holder',
noLicenseDescriptionError: 'Missing license description',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
CompletionDropdownMap,
DurationDropdownMap,
nonUniqueValue,
SHORT_LONG_ACTIVITY_MIDPOINT,
} from 'shared/constants';
import Checkbox from 'shared/views/form/Checkbox';
import { MasteryModelsNames } from 'shared/leUtils/MasteryModels';
Expand All @@ -123,7 +124,6 @@

const DEFAULT_SHORT_ACTIVITY = 600;
const DEFAULT_LONG_ACTIVITY = 3000;
const SHORT_LONG_ACTIVITY_MIDPOINT = 1860;

const defaultCompletionCriteriaModels = {
[ContentKindsNames.VIDEO]: CompletionCriteriaModels.TIME,
Expand Down Expand Up @@ -393,7 +393,7 @@
showCorrectCompletionOptions() {
if (this.kind) {
return CompletionOptionsDropdownMap[this.kind].map(model => ({
text: this.$tr(model),
text: this.translateMetadataString(model),
value: CompletionDropdownMap[model],
}));
}
Expand All @@ -402,7 +402,7 @@
selectableDurationOptions() {
return [
{
text: this.$tr(DurationDropdownMap.EXACT_TIME),
text: this.translateMetadataString(DurationDropdownMap.EXACT_TIME),
value: 'exactTime',
},
{
Expand Down Expand Up @@ -514,15 +514,6 @@
},
},
$trs: {
/* eslint-disable kolibri/vue-no-unused-translations */
allContent: 'Viewed in its entirety',
completeDuration: 'When time spent is equal to duration',
determinedByResource: 'Determined by the resource',
goal: 'When goal is met',
practiceQuiz: 'Practice quiz',
reference: 'Reference material',
/* eslint-enable */
exactTime: 'Time to complete',
referenceHint:
'Progress will not be tracked on reference material unless learners mark it as complete',
learnersCanMarkComplete: 'Allow learners to mark as complete',
Expand Down
Loading