Skip to content

Commit

Permalink
Merge branch 'release-4.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
oat-github-bot committed Feb 28, 2025
2 parents 7feb70c + 831c192 commit b89a18a
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 110 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oat-sa/tao-test-runner-qti",
"version": "4.2.0",
"version": "4.3.0",
"description": "TAO Test Runner QTI implementation",
"files": [
"dist",
Expand Down
23 changes: 23 additions & 0 deletions src/helpers/currentItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,29 @@ var currentItemHelper = {
return count > 0 && empty < count;
},

/**
* Tells is the current item is valid or not.
* Interaction should put `{ validity: { isValid: false } }` object to `itemState` if it's invalid.
* - note: min/max constraints are handled by `isAnswered` method
* - note: doesn't check if is answered or not.
* @param {Object} runner - testRunner instance
* @returns {Boolean}
*/
isValid: function isValid(runner) {
const itemRunner = runner.itemRunner;
if (itemRunner) {
const itemState = itemRunner && itemRunner.getState();
const declarations = currentItemHelper.getDeclarations(runner);

return !Object.values(declarations).some(function (declaration) {
const attributes = declaration.attributes || {};
const interactionState = itemState[attributes.identifier];
return interactionState && interactionState.validity && interactionState.validity.isValid === false;
});
}
return true;
},

/**
* Gets list of shared stimuli hrefs in the current item
*
Expand Down
23 changes: 14 additions & 9 deletions src/plugins/navigation/validateResponses.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,26 +69,31 @@ export default pluginFactory({
if (isInteracting && testRunnerOptions.enableValidateResponses) {
const currenItem = testRunner.getCurrentItem();
//@deprecated use validateResponses from testMap instead of the testContext
const validateResponses = typeof currenItem.validateResponses === 'boolean' ?
currenItem.validateResponses :
testContext.validateResponses;
const validateResponses =
typeof currenItem.validateResponses === 'boolean'
? currenItem.validateResponses
: testContext.validateResponses;

if (validateResponses) {
return new Promise((resolve, reject) => {
if (_.size(currentItemHelper.getDeclarations(testRunner)) === 0) {
return resolve();
}
if (currentItemHelper.isAnswered(testRunner, false)) {
if (currentItemHelper.isValid(testRunner) && currentItemHelper.isAnswered(testRunner, false)) {
return resolve();
}
if (!testRunner.getState('alerted.notallowed')) {
// Only show one alert for itemSessionControl
testRunner.setState('alerted.notallowed', true);
testRunner.trigger('alert.notallowed', __('A valid response to this item is required.'), () => {
testRunner.trigger('resumeitem');
reject();
testRunner.setState('alerted.notallowed', false);
});
testRunner.trigger(
'alert.notallowed',
__('A valid response to this item is required.'),
() => {
testRunner.trigger('resumeitem');
reject();
testRunner.setState('alerted.notallowed', false);
}
);
}
});
}
Expand Down
149 changes: 125 additions & 24 deletions test/helpers/currentItem/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2016-2022 (original work) Open Assessment Technologies SA ;
* Copyright (c) 2016-2025 (original work) Open Assessment Technologies SA ;
*/
/**
* @author Jean-Sébastien Conan <jean-sebastien.conan@vesperiagroup.com>
*/
define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentItemHelper) {
define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function (_, currentItemHelper) {
'use strict';

var messagesHelperApi = [
Expand All @@ -27,7 +27,8 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
{ title: 'toResponse' },
{ title: 'isQtiValueNull' },
{ title: 'isQuestionAnswered' },
{ title: 'isAnswered' }
{ title: 'isAnswered' },
{ title: 'isValid' }
];

/**
Expand All @@ -38,40 +39,43 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
* @param {Object} itemBdy
* @returns {Object}
*/
function runnerMock(responses, declarations, itemId, itemBdy) {
function runnerMock(responses, declarations, itemId, itemBdy, itemState) {
return {
itemRunner: {
_item: {
responses: declarations,
bdy: itemBdy,
itemIdentifier: itemId
},
getResponses: function() {
getResponses: function () {
return responses;
},
getState: function () {
return itemState;
}
}
};
}

QUnit.module('helpers/currentItem');

QUnit.test('module', function(assert) {
QUnit.test('module', function (assert) {
assert.expect(1);

assert.equal(typeof currentItemHelper, 'object', 'The currentItem helper module exposes an object');
});

QUnit.cases.init(messagesHelperApi).test('helpers/currentItem API ', function(data, assert) {
QUnit.cases.init(messagesHelperApi).test('helpers/currentItem API ', function (data, assert) {
assert.expect(1);

assert.equal(
typeof currentItemHelper[data.title],
'function',
`The currentItem helper expose a "${ data.title }" function`
`The currentItem helper expose a "${data.title}" function`
);
});

QUnit.test('helpers/currentItem.getDeclarations', function(assert) {
QUnit.test('helpers/currentItem.getDeclarations', function (assert) {
var declarations = {
responsedeclaration1: {
identifier: 'RESPONSE1',
Expand Down Expand Up @@ -107,7 +111,7 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
);
});

QUnit.test('helpers/currentItem.getResponseDeclaration', function(assert) {
QUnit.test('helpers/currentItem.getResponseDeclaration', function (assert) {
var declarations = {
responsedeclaration1: {
identifier: 'RESPONSE1',
Expand Down Expand Up @@ -148,7 +152,7 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
);
});

QUnit.test('helpers/currentItem.toResponse', function(assert) {
QUnit.test('helpers/currentItem.toResponse', function (assert) {
assert.expect(9);

assert.deepEqual(
Expand Down Expand Up @@ -178,12 +182,26 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
);
assert.deepEqual(
currentItemHelper.toResponse(['choice_2 choice_3', 'choice_2 choice_4'], 'directedPair', 'multiple'),
{ list: { directedPair: [['choice_2', 'choice_3'], ['choice_2', 'choice_4']] } },
{
list: {
directedPair: [
['choice_2', 'choice_3'],
['choice_2', 'choice_4']
]
}
},
'The helper has built the right response'
);
assert.deepEqual(
currentItemHelper.toResponse(['choice_2 choice_3', 'choice_2 choice_4'], 'pair', 'multiple'),
{ list: { pair: [['choice_2', 'choice_3'], ['choice_2', 'choice_4']] } },
{
list: {
pair: [
['choice_2', 'choice_3'],
['choice_2', 'choice_4']
]
}
},
'The helper has built the right response'
);
assert.deepEqual(
Expand All @@ -198,7 +216,7 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
);
});

QUnit.test('helpers/currentItem.isQtiValueNull', function(assert) {
QUnit.test('helpers/currentItem.isQtiValueNull', function (assert) {
assert.expect(5);

assert.equal(currentItemHelper.isQtiValueNull(null, 'string', 'single'), true, 'The response should be null');
Expand All @@ -224,7 +242,7 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
);
});

QUnit.test('helpers/currentItem.isQuestionAnswered', function(assert) {
QUnit.test('helpers/currentItem.isQuestionAnswered', function (assert) {
assert.expect(21);

// Null
Expand Down Expand Up @@ -341,7 +359,7 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
);
});

QUnit.test('helpers/currentItem.isAnswered', function(assert) {
QUnit.test('helpers/currentItem.isAnswered', function (assert) {
var declarations = {
responsedeclaration1: {
identifier: 'RESPONSE1',
Expand All @@ -366,7 +384,7 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
defaultValue: []
}
};
var fullyResponded = { RESPONSE1: { base: { string: 'bar' } }, RESPONSE2: { base: { string: 'foo' } } };
var fullyResponded = { RESPONSE1: { base: { string: 'bar' } }, RESPONSE2: { base: { string: 'foo' } } };
var partiallyResponded = { RESPONSE1: { base: null }, RESPONSE2: { base: { string: 'foo' } } };
var notResponded = { RESPONSE1: { base: null }, RESPONSE2: { base: null } };

Expand All @@ -378,13 +396,37 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI

assert.expect(6);

assert.equal(currentItemHelper.isAnswered(fullyRespondedRunner), true, 'The fully answered item should be answered');
assert.equal(currentItemHelper.isAnswered(partiallyRespondedRunner), true, 'The partially answered item should be answered');
assert.equal(currentItemHelper.isAnswered(notRespondedRunner), false, 'The unanswered item should not be answered');
assert.equal(
currentItemHelper.isAnswered(fullyRespondedRunner),
true,
'The fully answered item should be answered'
);
assert.equal(
currentItemHelper.isAnswered(partiallyRespondedRunner),
true,
'The partially answered item should be answered'
);
assert.equal(
currentItemHelper.isAnswered(notRespondedRunner),
false,
'The unanswered item should not be answered'
);

assert.equal(currentItemHelper.isAnswered(fullyRespondedRunner, partially), true, 'The fully answered item should be answered');
assert.equal(currentItemHelper.isAnswered(partiallyRespondedRunner, partially), false, 'The partially answered item should not be answered');
assert.equal(currentItemHelper.isAnswered(notRespondedRunner, partially), false, 'The unanswered item should not be answered');
assert.equal(
currentItemHelper.isAnswered(fullyRespondedRunner, partially),
true,
'The fully answered item should be answered'
);
assert.equal(
currentItemHelper.isAnswered(partiallyRespondedRunner, partially),
false,
'The partially answered item should not be answered'
);
assert.equal(
currentItemHelper.isAnswered(notRespondedRunner, partially),
false,
'The unanswered item should not be answered'
);
});

QUnit.cases
Expand Down Expand Up @@ -425,12 +467,71 @@ define(['lodash', 'taoQtiTest/runner/helpers/currentItem'], function(_, currentI
expectedResult: ['http://path/to/something.xml']
}
])
.test('helpers/currentItem.getStimuliHrefs', function(caseData, assert) {
.test('helpers/currentItem.getStimuliHrefs', function (caseData, assert) {
var runner = runnerMock(null, null, caseData.itemId, caseData.itemBdy);
var hrefs = currentItemHelper.getStimuliHrefs(runner, caseData.itemId);

assert.expect(1);

assert.deepEqual(hrefs, caseData.expectedResult, 'getStimuli returns correct value');
});

QUnit.test('helpers/currentItem.isValid', function (assert) {
const declarations = {
responsedeclaration1: {
identifier: 'RESPONSE1',
serial: 'responsedeclaration1',
qtiClass: 'responseDeclaration',
attributes: {
identifier: 'RESPONSE1',
cardinality: 'single',
baseType: 'integer'
},
defaultValue: []
},
responsedeclaration2: {
identifier: 'RESPONSE2',
serial: 'responsedeclaration2',
qtiClass: 'responseDeclaration',
attributes: {
identifier: 'RESPONSE2',
cardinality: 'single',
baseType: 'float'
},
defaultValue: []
}
};
const responses = { RESPONSE1: { base: { integer: '3' } }, RESPONSE2: { base: { float: '3.14' } } };

const oneInvalidRunner = runnerMock(responses, declarations, null, null, {
RESPONSE1: { response: { base: { integer: '3' } }, validity: { isValid: false } },
RESPONSE2: { response: { base: { float: '3.14' } } }
});
const noInvalidRunner = runnerMock(responses, declarations, null, null, {
RESPONSE1: { response: { base: { integer: '3' } }, validity: { isValid: true } },
RESPONSE2: { response: { base: { float: '3.14' } } }
});
const noInvalidNoValidityRunner = runnerMock(responses, declarations, null, null, {
RESPONSE1: { response: { base: { integer: '3' } } },
RESPONSE2: { response: { base: { float: '3.14' } } }
});
const noDeclarationsRunner = runnerMock({}, {}, null, null, {});

assert.equal(
currentItemHelper.isValid(oneInvalidRunner),
false,
'item with isValid:false in validity state is invalid'
);
assert.equal(
currentItemHelper.isValid(noInvalidRunner),
true,
'item with isValid:true in validity state is invalid'
);
assert.equal(
currentItemHelper.isValid(noInvalidNoValidityRunner),
true,
'item without validity state defined is valid'
);
assert.equal(currentItemHelper.isValid(noDeclarationsRunner), true, 'item without declarations is valid');
});
});
Loading

0 comments on commit b89a18a

Please sign in to comment.