From 900260f046e68baca79583ec4afbb97d51fc016a Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 8 Sep 2017 08:21:38 +0200 Subject: [PATCH] OO-2910: allow no answers for match and match with drag and drop --- .../model/xml/AssessmentItemFactory.java | 86 ++++ .../MatchAssessmentItemBuilder.java | 295 +++++++------- .../interactions/MatchEditorController.java | 3 +- .../java/org/olat/selenium/ImsQTI21Test.java | 375 +++++++++++++++++- .../org/olat/selenium/page/qti/QTI21Page.java | 6 + 5 files changed, 614 insertions(+), 151 deletions(-) diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java index 8824040add4..081db4707a3 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java @@ -48,6 +48,7 @@ import uk.ac.ed.ph.jqtiplus.node.content.xhtml.object.Object; import uk.ac.ed.ph.jqtiplus.node.content.xhtml.text.P; import uk.ac.ed.ph.jqtiplus.node.expression.general.BaseValue; import uk.ac.ed.ph.jqtiplus.node.expression.general.Correct; +import uk.ac.ed.ph.jqtiplus.node.expression.general.MapResponse; import uk.ac.ed.ph.jqtiplus.node.expression.general.Variable; import uk.ac.ed.ph.jqtiplus.node.expression.operator.And; import uk.ac.ed.ph.jqtiplus.node.expression.operator.Gt; @@ -79,6 +80,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.MapEntry; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.Mapping; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseCondition; +import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseConditionChild; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseElse; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseElseIf; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseIf; @@ -184,6 +186,90 @@ public class AssessmentItemFactory { outcomeDeclarations.getOutcomeDeclarations().add(feedbackOutcomeDeclaration); } + /* + <setOutcomeValue identifier="FEEDBACKBASIC"> + <baseValue baseType="identifier"> + correct + </baseValue> + </setOutcomeValue> + */ + public static void appendSetOutcomeFeedbackCorrect(ResponseConditionChild responseCondition) { + SetOutcomeValue correctOutcomeValue = new SetOutcomeValue(responseCondition); + correctOutcomeValue.setIdentifier(QTI21Constants.FEEDBACKBASIC_IDENTIFIER); + responseCondition.getResponseRules().add(correctOutcomeValue); + + BaseValue correctValue = new BaseValue(correctOutcomeValue); + correctValue.setBaseTypeAttrValue(BaseType.IDENTIFIER); + correctValue.setSingleValue(QTI21Constants.CORRECT_IDENTIFIER_VALUE); + correctOutcomeValue.setExpression(correctValue); + } + + /* + <setOutcomeValue identifier="FEEDBACKBASIC"> + <baseValue baseType="identifier">incorrect</baseValue> + </setOutcomeValue> + */ + public static void appendSetOutcomeFeedbackIncorrect(ResponseConditionChild responseCondition) { + SetOutcomeValue incorrectOutcomeValue = new SetOutcomeValue(responseCondition); + incorrectOutcomeValue.setIdentifier(QTI21Constants.FEEDBACKBASIC_IDENTIFIER); + responseCondition.getResponseRules().add(incorrectOutcomeValue); + + BaseValue incorrectValue = new BaseValue(incorrectOutcomeValue); + incorrectValue.setBaseTypeAttrValue(BaseType.IDENTIFIER); + incorrectValue.setSingleValue(QTI21Constants.INCORRECT_IDENTIFIER_VALUE); + incorrectOutcomeValue.setExpression(incorrectValue); + } + + /* + <setOutcomeValue identifier="SCORE"> + <sum> + <variable identifier="SCORE"/> + <mapResponse identifier="RESPONSE_1"/> + </sum> + </setOutcomeValue> + */ + public static void appendSetOutcomeScoreMapResponse(ResponseConditionChild responseCondition, Identifier responseIdentifier) { + SetOutcomeValue scoreOutcome = new SetOutcomeValue(responseCondition); + scoreOutcome.setIdentifier(QTI21Constants.SCORE_IDENTIFIER); + responseCondition.getResponseRules().add(scoreOutcome); + + Sum sum = new Sum(scoreOutcome); + scoreOutcome.getExpressions().add(sum); + + Variable scoreVar = new Variable(sum); + scoreVar.setIdentifier(QTI21Constants.SCORE_CLX_IDENTIFIER); + sum.getExpressions().add(scoreVar); + + MapResponse mapResponse = new MapResponse(sum); + mapResponse.setIdentifier(responseIdentifier); + sum.getExpressions().add(mapResponse); + } + + /* + <setOutcomeValue identifier="SCORE"> + <sum> + <variable identifier="SCORE"/> + <variable identifier="MAXSCORE"/> + </sum> + </setOutcomeValue> + */ + public static void appendSetOutcomeScoreMaxScore(ResponseConditionChild responseCondition) { + SetOutcomeValue scoreOutcomeValue = new SetOutcomeValue(responseCondition); + scoreOutcomeValue.setIdentifier(QTI21Constants.SCORE_IDENTIFIER); + responseCondition.getResponseRules().add(scoreOutcomeValue); + + Sum sum = new Sum(scoreOutcomeValue); + scoreOutcomeValue.getExpressions().add(sum); + + Variable scoreVar = new Variable(sum); + scoreVar.setIdentifier(QTI21Constants.SCORE_CLX_IDENTIFIER); + sum.getExpressions().add(scoreVar); + + Variable maxScoreVar = new Variable(sum); + maxScoreVar.setIdentifier(QTI21Constants.MAXSCORE_CLX_IDENTIFIER); + sum.getExpressions().add(maxScoreVar); + } + public static HotspotInteraction appendHotspotInteraction(ItemBody itemBody, Identifier responseDeclarationId, Identifier correctResponseId) { HotspotInteraction hotspotInteraction = new HotspotInteraction(itemBody); hotspotInteraction.setResponseIdentifier(responseDeclarationId); diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/MatchAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/MatchAssessmentItemBuilder.java index 59a7d45439e..4f74399f851 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/MatchAssessmentItemBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/MatchAssessmentItemBuilder.java @@ -23,6 +23,10 @@ import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendAssociati import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendDefaultItemBody; import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendDefaultOutcomeDeclarations; import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendMatchInteraction; +import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendSetOutcomeFeedbackCorrect; +import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendSetOutcomeFeedbackIncorrect; +import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendSetOutcomeScoreMapResponse; +import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendSetOutcomeScoreMaxScore; import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.createMatchResponseDeclaration; import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.createResponseProcessing; @@ -44,14 +48,10 @@ import org.olat.ims.qti21.model.xml.interactions.SimpleChoiceAssessmentItemBuild import uk.ac.ed.ph.jqtiplus.group.NodeGroupList; import uk.ac.ed.ph.jqtiplus.node.content.ItemBody; import uk.ac.ed.ph.jqtiplus.node.content.basic.Block; -import uk.ac.ed.ph.jqtiplus.node.expression.general.BaseValue; import uk.ac.ed.ph.jqtiplus.node.expression.general.Correct; -import uk.ac.ed.ph.jqtiplus.node.expression.general.MapResponse; import uk.ac.ed.ph.jqtiplus.node.expression.general.Variable; import uk.ac.ed.ph.jqtiplus.node.expression.operator.IsNull; import uk.ac.ed.ph.jqtiplus.node.expression.operator.Match; -import uk.ac.ed.ph.jqtiplus.node.expression.operator.Not; -import uk.ac.ed.ph.jqtiplus.node.expression.operator.Sum; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.CorrectResponse; import uk.ac.ed.ph.jqtiplus.node.item.interaction.MatchInteraction; @@ -62,17 +62,14 @@ import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.Mapping; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseCondition; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseElse; -import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseElseIf; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseIf; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseProcessing; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseRule; -import uk.ac.ed.ph.jqtiplus.node.item.response.processing.SetOutcomeValue; import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration; import uk.ac.ed.ph.jqtiplus.node.shared.FieldValue; import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer; import uk.ac.ed.ph.jqtiplus.types.ComplexReferenceIdentifier; import uk.ac.ed.ph.jqtiplus.types.Identifier; -import uk.ac.ed.ph.jqtiplus.value.BaseType; import uk.ac.ed.ph.jqtiplus.value.DirectedPairValue; import uk.ac.ed.ph.jqtiplus.value.SingleValue; @@ -447,7 +444,11 @@ public class MatchAssessmentItemBuilder extends AssessmentItemBuilder { ResponseCondition rule = new ResponseCondition(assessmentItem.getResponseProcessing()); responseRules.add(0, rule); if(scoreEvaluation == ScoreEvaluation.perAnswer) { - buildMainScoreRulePerAnswer(rule); + if(associations.isEmpty()) { + buildMainScoreRulePerAnswerNoAnswers(rule); + } else { + buildMainScoreRulePerAnswer(rule); + } } else { buildMainScoreRuleAllCorrectAnswers(rule); } @@ -455,102 +456,140 @@ public class MatchAssessmentItemBuilder extends AssessmentItemBuilder { @Override protected void buildModalFeedbacksAndHints(List<OutcomeDeclaration> outcomeDeclarations, List<ResponseRule> responseRules) { - if(correctFeedback != null || incorrectFeedback != null) { - if(scoreEvaluation == ScoreEvaluation.perAnswer) { - ResponseCondition responseCondition = AssessmentItemFactory.createModalFeedbackResponseConditionByScore(assessmentItem.getResponseProcessing()); - responseRules.add(responseCondition); - } - } - super.buildModalFeedbacksAndHints(outcomeDeclarations, responseRules); } - private void buildMainScoreRulePerAnswer(ResponseCondition rule) { + /** + * Special case where no answers are correct:<br> + * <ul> + * <li>If no answers chosen: maxScore + correct + * <li>If answer chosen: map score of answers + incorrect + * </ul> + * + * @param rule + */ + private void buildMainScoreRulePerAnswerNoAnswers(ResponseCondition rule) { /* - <responseCondition> - <responseIf> - <not> - <isNull> - <variable identifier="RESPONSE_4953445" /> - </isNull> - </not> - <setOutcomeValue identifier="SCORE"> - <sum> - <variable identifier="SCORE" /><mapResponse identifier="RESPONSE_4953445" /> - </sum> - </setOutcomeValue> - <setOutcomeValue identifier="FEEDBACKBASIC"> - <baseValue baseType="identifier"> - incorrect - </baseValue> - </setOutcomeValue> - </responseIf> - </responseCondition> - <responseCondition> - <responseIf> - <and> - <not> - <match> - <variable identifier="FEEDBACKBASIC" /> - <baseValue baseType="identifier"> - empty - </baseValue> - </match> - </not> - <equal toleranceMode="exact"> - <variable identifier="SCORE" /><variable identifier="MAXSCORE" /> - </equal> - </and> - <setOutcomeValue identifier="FEEDBACKBASIC"> - <baseValue baseType="identifier"> - correct - </baseValue> - </setOutcomeValue> - </responseIf> - </responseCondition> + <responseIf> + <isNull> + <variable identifier="RESPONSE_1"/> + </isNull> + <setOutcomeValue identifier="SCORE"> + <sum> + <variable identifier="SCORE"/> + <variable identifier="MAXSCORE"/> + </sum> + </setOutcomeValue> + <setOutcomeValue identifier="FEEDBACKBASIC"> + <baseValue baseType="identifier">correct</baseValue> + </setOutcomeValue> + </responseIf> + <responseElse> + <setOutcomeValue identifier="SCORE"> + <sum> + <variable identifier="SCORE"/> + <mapResponse identifier="RESPONSE_1"/> + </sum> + </setOutcomeValue> + <setOutcomeValue identifier="FEEDBACKBASIC"> + <baseValue baseType="identifier">incorrect</baseValue> + </setOutcomeValue> + </responseElse> */ - //if no response ResponseIf responseIf = new ResponseIf(rule); rule.setResponseIf(responseIf); - - Not not = new Not(responseIf); - responseIf.getExpressions().add(not); - IsNull isNull = new IsNull(not); - not.getExpressions().add(isNull); + IsNull isNull = new IsNull(responseIf); + responseIf.getExpressions().add(isNull); Variable variable = new Variable(isNull); variable.setIdentifier(ComplexReferenceIdentifier.parseString(responseIdentifier.toString())); isNull.getExpressions().add(variable); - {// outcome score - SetOutcomeValue scoreOutcome = new SetOutcomeValue(responseIf); - scoreOutcome.setIdentifier(QTI21Constants.SCORE_IDENTIFIER); - responseIf.getResponseRules().add(scoreOutcome); - - Sum sum = new Sum(scoreOutcome); - scoreOutcome.getExpressions().add(sum); - - Variable scoreVar = new Variable(sum); - scoreVar.setIdentifier(QTI21Constants.SCORE_CLX_IDENTIFIER); - sum.getExpressions().add(scoreVar); + //outcome sum score + max score + appendSetOutcomeScoreMaxScore(responseIf); - MapResponse mapResponse = new MapResponse(sum); - mapResponse.setIdentifier(responseIdentifier); - sum.getExpressions().add(mapResponse); - } + //outcome correct feedback + appendSetOutcomeFeedbackCorrect(responseIf); - {//outcome feedback - SetOutcomeValue incorrectOutcomeValue = new SetOutcomeValue(responseIf); - incorrectOutcomeValue.setIdentifier(QTI21Constants.FEEDBACKBASIC_IDENTIFIER); - responseIf.getResponseRules().add(incorrectOutcomeValue); - - BaseValue incorrectValue = new BaseValue(incorrectOutcomeValue); - incorrectValue.setBaseTypeAttrValue(BaseType.IDENTIFIER); - incorrectValue.setSingleValue(QTI21Constants.INCORRECT_IDENTIFIER_VALUE); - incorrectOutcomeValue.setExpression(incorrectValue); - } + ResponseElse responseElse = new ResponseElse(rule); + rule.setResponseElse(responseElse); + // outcome score + appendSetOutcomeScoreMapResponse(responseElse, responseIdentifier); + //outcome incorrect feedback + appendSetOutcomeFeedbackIncorrect(responseElse); + } + + /** + * Case with some correct answers and scoring per answer: + * <ul> + * <li>All correct: calculate score + correct + * <li>Else: calculate score + incorrect + * </ul> + * + * @param rule + */ + private void buildMainScoreRulePerAnswer(ResponseCondition rule) { + /* + <responseCondition> + <responseIf> + <match> + <variable identifier="RESPONSE_1"/> + <correct identifier="RESPONSE_1"/> + </match> + <setOutcomeValue identifier="SCORE"> + <sum> + <variable identifier="SCORE"/> + <mapResponse identifier="RESPONSE_1"/> + </sum> + </setOutcomeValue> + <setOutcomeValue identifier="FEEDBACKBASIC"> + <baseValue baseType="identifier">correct</baseValue> + </setOutcomeValue> + </responseIf> + <responseElse> + <setOutcomeValue identifier="SCORE"> + <sum> + <variable identifier="SCORE"/> + <mapResponse identifier="RESPONSE_1"/> + </sum> + </setOutcomeValue> + <setOutcomeValue identifier="FEEDBACKBASIC"> + <baseValue baseType="identifier">incorrect</baseValue> + </setOutcomeValue> + </responseElse> + </responseCondition> + */ + + //if no response + ResponseIf responseIf = new ResponseIf(rule); + rule.setResponseIf(responseIf); + Match match = new Match(responseIf); + responseIf.getExpressions().add(match); + + Variable responseVar = new Variable(match); + ComplexReferenceIdentifier choiceResponseIdentifier + = ComplexReferenceIdentifier.parseString(responseIdentifier.toString()); + responseVar.setIdentifier(choiceResponseIdentifier); + match.getExpressions().add(responseVar); + + Correct correct = new Correct(match); + correct.setIdentifier(choiceResponseIdentifier); + match.getExpressions().add(correct); + + // outcome score + appendSetOutcomeScoreMapResponse(responseIf, responseIdentifier); + //outcome correct feedback + appendSetOutcomeFeedbackCorrect(responseIf); + + ResponseElse responseElse = new ResponseElse(rule); + rule.setResponseElse(responseElse); + + // outcome score + appendSetOutcomeScoreMapResponse(responseElse, responseIdentifier); + // outcome incorrect feedback + appendSetOutcomeFeedbackIncorrect(responseElse); } private void buildMainScoreRuleAllCorrectAnswers(ResponseCondition rule) { @@ -599,82 +638,40 @@ public class MatchAssessmentItemBuilder extends AssessmentItemBuilder { ResponseIf responseIf = new ResponseIf(rule); rule.setResponseIf(responseIf); - {//if no response + // match the correct answers (or null if there are no associations) + if(associations.isEmpty()) { IsNull isNull = new IsNull(responseIf); responseIf.getExpressions().add(isNull); - Variable variable = new Variable(isNull); - variable.setIdentifier(ComplexReferenceIdentifier.parseString(responseIdentifier.toString())); - isNull.getExpressions().add(variable); - - SetOutcomeValue incorrectOutcomeValue = new SetOutcomeValue(responseIf); - incorrectOutcomeValue.setIdentifier(QTI21Constants.FEEDBACKBASIC_IDENTIFIER); - responseIf.getResponseRules().add(incorrectOutcomeValue); - - BaseValue incorrectValue = new BaseValue(incorrectOutcomeValue); - incorrectValue.setBaseTypeAttrValue(BaseType.IDENTIFIER); - incorrectValue.setSingleValue(QTI21Constants.EMPTY_IDENTIFIER_VALUE); - incorrectOutcomeValue.setExpression(incorrectValue); - } - - ResponseElseIf responseElseIf = new ResponseElseIf(rule); - rule.getResponseElseIfs().add(responseElseIf); - - {// match the correct answers - Match match = new Match(responseElseIf); - responseElseIf.getExpressions().add(match); + Variable responseVar = new Variable(isNull); + ComplexReferenceIdentifier choiceResponseIdentifier + = ComplexReferenceIdentifier.parseString(responseIdentifier.toString()); + responseVar.setIdentifier(choiceResponseIdentifier); + isNull.getExpressions().add(responseVar); + } else { + Match match = new Match(responseIf); + responseIf.getExpressions().add(match); - Variable scoreVar = new Variable(match); + Variable responseVar = new Variable(match); ComplexReferenceIdentifier choiceResponseIdentifier = ComplexReferenceIdentifier.parseString(responseIdentifier.toString()); - scoreVar.setIdentifier(choiceResponseIdentifier); - match.getExpressions().add(scoreVar); + responseVar.setIdentifier(choiceResponseIdentifier); + match.getExpressions().add(responseVar); Correct correct = new Correct(match); correct.setIdentifier(choiceResponseIdentifier); match.getExpressions().add(correct); } - {//outcome score - SetOutcomeValue scoreOutcomeValue = new SetOutcomeValue(responseElseIf); - scoreOutcomeValue.setIdentifier(QTI21Constants.SCORE_IDENTIFIER); - responseElseIf.getResponseRules().add(scoreOutcomeValue); + //outcome score + max score + appendSetOutcomeScoreMaxScore(responseIf); - Sum sum = new Sum(scoreOutcomeValue); - scoreOutcomeValue.getExpressions().add(sum); - - Variable scoreVar = new Variable(sum); - scoreVar.setIdentifier(QTI21Constants.SCORE_CLX_IDENTIFIER); - sum.getExpressions().add(scoreVar); - - Variable maxScoreVar = new Variable(sum); - maxScoreVar.setIdentifier(QTI21Constants.MAXSCORE_CLX_IDENTIFIER); - sum.getExpressions().add(maxScoreVar); - } - - {//outcome feedback - SetOutcomeValue correctOutcomeValue = new SetOutcomeValue(responseElseIf); - correctOutcomeValue.setIdentifier(QTI21Constants.FEEDBACKBASIC_IDENTIFIER); - responseElseIf.getResponseRules().add(correctOutcomeValue); - - BaseValue correctValue = new BaseValue(correctOutcomeValue); - correctValue.setBaseTypeAttrValue(BaseType.IDENTIFIER); - correctValue.setSingleValue(QTI21Constants.CORRECT_IDENTIFIER_VALUE); - correctOutcomeValue.setExpression(correctValue); - } + //outcome correct feedback + appendSetOutcomeFeedbackCorrect(responseIf); ResponseElse responseElse = new ResponseElse(rule); rule.setResponseElse(responseElse); - - {// outcome feedback - SetOutcomeValue incorrectOutcomeValue = new SetOutcomeValue(responseElse); - incorrectOutcomeValue.setIdentifier(QTI21Constants.FEEDBACKBASIC_IDENTIFIER); - responseElse.getResponseRules().add(incorrectOutcomeValue); - - BaseValue incorrectValue = new BaseValue(incorrectOutcomeValue); - incorrectValue.setBaseTypeAttrValue(BaseType.IDENTIFIER); - incorrectValue.setSingleValue(QTI21Constants.INCORRECT_IDENTIFIER_VALUE); - incorrectOutcomeValue.setExpression(incorrectValue); - } + // outcome incorrect feedback + appendSetOutcomeFeedbackIncorrect(responseElse); } } diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchEditorController.java index 71221c85dc4..bae0eae92df 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/MatchEditorController.java @@ -243,7 +243,7 @@ public class MatchEditorController extends FormBasicController { } commitTemporaryAssociations(ureq); - + /* if(singleMultiEl.isOneSelected() && singleMultiEl.isSelected(0)) { Map<String,String> sourseTargetMap = new HashMap<>(); String[] directedPairsIds = ureq.getHttpReq().getParameterValues("qtiworks_response_" + itemBuilder.getResponseIdentifier()); @@ -278,6 +278,7 @@ public class MatchEditorController extends FormBasicController { } } } + */ if(layoutEl != null) { layoutEl.clearError(); diff --git a/src/test/java/org/olat/selenium/ImsQTI21Test.java b/src/test/java/org/olat/selenium/ImsQTI21Test.java index ce929373d6b..57cbea9b2b5 100644 --- a/src/test/java/org/olat/selenium/ImsQTI21Test.java +++ b/src/test/java/org/olat/selenium/ImsQTI21Test.java @@ -1611,7 +1611,193 @@ public class ImsQTI21Test { .assertOnAssessmentTestScore(10);// 4 points from the first question, 6 from the second } - + /** + * An author make a test with 2 matches. A match with "multiple selection" + * and score "all answers", a second with "single selection" and score + * "per answers". They are distractors, the assessed user must let them blank.<br> + * A first user make the test, but doesn't answer all questions + * correctly, log out and a second user make the perfect test. + * + * @param authorLoginPage + * @param participantBrowser + * @throws IOException + * @throws URISyntaxException + */ + @Test + @RunAsClient + public void qti21EditorMatch_distractors(@InitialPage LoginPage authorLoginPage, + @Drone @User WebDriver participantBrowser) + throws IOException, URISyntaxException { + UserVO author = new UserRestClient(deploymentUrl).createAuthor(); + UserVO rei = new UserRestClient(deploymentUrl).createRandomUser("Rei"); + UserVO melissa = new UserRestClient(deploymentUrl).createRandomUser("Melissa"); + authorLoginPage.loginAs(author.getLogin(), author.getPassword()); + + String qtiTestTitle = "Match QTI 2.1 " + UUID.randomUUID(); + navBar + .openAuthoringEnvironment() + .createQTI21Test(qtiTestTitle) + .clickToolbarBack(); + + QTI21Page qtiPage = QTI21Page + .getQTI12Page(browser); + QTI21EditorPage qtiEditor = qtiPage + .edit(); + //start a blank test + qtiEditor + .selectNode("Single choice") + .deleteNode(); + + //add a match, multiple selection + QTI21MatchEditorPage matchEditor = qtiEditor + .addMatch(); + matchEditor + .setSource(0, "Eclipse") + .setSource(1, "nano") + .setTarget(0, "IDE") + .setTarget(1, "WordProcessor") + .addColumn() + .setTarget(2, "CAD") + .save(); + // change max score + matchEditor + .selectScores() + .setMaxScore("4") + .save(); + // set some feedbacks + matchEditor + .selectFeedbacks() + .setHint("Hint", "This is only an hint") + .setCorrectSolution("Correct solution", "This is the correct solution") + .setCorrectFeedback("Correct feedback", "This is correct") + .setIncorrectFeedback("Incorrect", "Your answer is not correct") + .save(); + + // second match + matchEditor = qtiEditor + .addMatch() + .setSingleChoices() + .setSource(0, "Java") + .setSource(1, "C") + .addRow() + .setSource(2, "PHP") + .setTarget(0, "Lynx") + .setTarget(1, "Netscape") + .addColumn() + .setTarget(2, "Pixel") + .save(); + // select score "per answer" and set the scores + matchEditor + .selectScores() + .selectAssessmentMode(ScoreEvaluation.perAnswer) + .setMaxScore("6") + .setScore(0, 0, "0.0") + .setScore(0, 1, "0.0") + .setScore(0, 2, "1.0") + .setScore(1, 0, "0.0") + .setScore(1, 1, "1.0") + .setScore(1, 2, "0.0") + .setScore(2, 0, "2.0") + .setScore(2, 1, "0.0") + .setScore(2, 2, "-0.5") + .save(); + matchEditor + .selectFeedbacks() + .setHint("Hint", "The hint") + .setCorrectSolution("Correct solution", "This is the correct solution") + .setCorrectFeedback("Correct feedback", "This is correct") + .setIncorrectFeedback("Incorrect", "Your answer is not correct") + .save(); + + qtiPage + .clickToolbarBack(); + // access to all + qtiPage + .accessConfiguration() + .setUserAccess(UserAccess.guest) + .clickToolbarBack(); + // show results + qtiPage + .options() + .showResults(Boolean.TRUE, QTI21AssessmentResultsOptions.allOptions()) + .save(); + + //a user search the content package + LoginPage reiLoginPage = LoginPage.getLoginPage(participantBrowser, deploymentUrl); + reiLoginPage + .loginAs(rei.getLogin(), rei.getPassword()) + .resume(); + NavigationPage reiNavBar = new NavigationPage(participantBrowser); + reiNavBar + .openMyCourses() + .openSearch() + .extendedSearch(qtiTestTitle) + .select(qtiTestTitle) + .start(); + + // make the test + QTI21Page reiQtiPage = QTI21Page + .getQTI12Page(participantBrowser); + reiQtiPage + .assertOnAssessmentItem() + .answerMatch("Eclipse", "WordProcessor", true) + .answerMatch("nano", "CAD", true) + .saveAnswer() + .assertFeedback("Incorrect") + .assertCorrectSolution("Correct solution") + .hint() + .assertFeedback("Hint") + .answerMatch("nano", "CAD", false) + .answerMatch("Eclipse", "WordProcessor", false) + .saveAnswer() + .assertFeedback("Correct feedback") + .nextAnswer() + .answerMatch("Java", "Pixel", true) + .answerMatch("C", "Lynx", true) + .answerMatch("PHP", "Pixel", true) + .saveAnswer() + .assertCorrectSolution("Correct solution") + .assertFeedback("Incorrect") + .endTest() + .assertOnAssessmentResults() + .assertOnAssessmentTestScore("4.5");// 4 points from the first question, 0.5 from the second + + //a second user search the content package + LoginPage melLoginPage = LoginPage.getLoginPage(participantBrowser, deploymentUrl); + melLoginPage + .loginAs(melissa.getLogin(), melissa.getPassword()) + .resume(); + NavigationPage melNavBar = new NavigationPage(participantBrowser); + melNavBar + .openMyCourses() + .openSearch() + .extendedSearch(qtiTestTitle) + .select(qtiTestTitle) + .start(); + + // make the test + QTI21Page + .getQTI12Page(participantBrowser) + .assertOnAssessmentItem() + .saveAnswer() + .assertFeedback("Correct feedback") + .nextAnswer() + .answerMatch("Java", "Pixel", true) + .answerMatch("C", "Pixel", true) + .answerMatch("PHP", "Lynx", true) + .saveAnswer() + .assertFeedback("Incorrect") + .assertCorrectSolution("Correct solution") + .answerMatch("Java", "Pixel", false) + .answerMatch("C", "Pixel", false) + .answerMatch("PHP", "Lynx", false) + .saveAnswer() + .assertFeedback("Correct feedback") + .endTest() + .assertOnAssessmentResults() + .assertOnAssessmentTestScore(10);// 4 points from the first question, 6 from the second + } + /** * An author make a test with 2 match of the drag and drop variety * with feedbacks.<br> @@ -1801,6 +1987,193 @@ public class ImsQTI21Test { .assertOnAssessmentTestScore(11);// 4 points from the first question, 7 from the second } + /** + * An author make a test with 2 match of the drag and drop variety + * with feedbacks but as distractor. The assessed user need to let them + * blank to have the max. score.<br> + * A first user make the test, check the feedbacks but make an error + * and score the maximum. A second user answers all the questions + * correctly. + * + * @param authorLoginPage + * @param participantBrowser + * @throws IOException + * @throws URISyntaxException + */ + @Test + @RunAsClient + public void qti21EditorMatchDragAndDrop_distractors(@InitialPage LoginPage authorLoginPage, + @Drone @User WebDriver participantBrowser) + throws IOException, URISyntaxException { + UserVO author = new UserRestClient(deploymentUrl).createAuthor(); + UserVO asuka = new UserRestClient(deploymentUrl).createRandomUser("Asuka"); + UserVO chara = new UserRestClient(deploymentUrl).createRandomUser("Chara"); + authorLoginPage.loginAs(author.getLogin(), author.getPassword()); + + String qtiTestTitle = "Match DnD QTI 2.1 " + UUID.randomUUID(); + navBar + .openAuthoringEnvironment() + .createQTI21Test(qtiTestTitle) + .clickToolbarBack(); + + QTI21Page qtiPage = QTI21Page + .getQTI12Page(browser); + QTI21EditorPage qtiEditor = qtiPage + .edit(); + //start a blank test + qtiEditor + .selectNode("Single choice") + .deleteNode(); + + //add a match, multiple selection + QTI21MatchEditorPage matchEditor = qtiEditor + .addMatchDragAndDrop(); + matchEditor + .setSource(0, "Einstein") + .setSource(1, "Planck") + .addRow() + .setSource(2, "Euler") + .setTarget(0, "Chemistry") + .setTarget(1, "Philosophy") + .save(); + // change max score + matchEditor + .selectScores() + .setMaxScore("4") + .save(); + // set some feedbacks + matchEditor + .selectFeedbacks() + .setHint("Hint", "Euler come from Switzerland") + .setCorrectSolution("Correct solution", "The correct solution is simple") + .setCorrectFeedback("Correct feedback", "You are right") + .setIncorrectFeedback("Incorrect", "Your answer is not exactly correct") + .save(); + + // second match + matchEditor = qtiEditor + .addMatchDragAndDrop() + .setSingleChoices() + .setSource(0, "Euler") + .setSource(1, "Broglie") + .addRow() + .setSource(2, "Konrad") + .setTarget(0, "Chemistry") + .setTarget(1, "Biology") + .addColumn() + .setTarget(2, "Astrology") + .save(); + // select score "per answer" and set the scores + matchEditor + .selectScores() + .selectAssessmentMode(ScoreEvaluation.perAnswer) + .setMaxScore("8") + .setScore(0, 0, "1.0") + .setScore(0, 1, "0.0") + .setScore(0, 2, "0.0") + .setScore(1, 0, "0.0") + .setScore(1, 1, "0.0") + .setScore(1, 2, "-0.5") + .setScore(2, 0, "0.0") + .setScore(2, 1, "2.0") + .setScore(2, 2, "0.0") + .save(); + matchEditor + .selectFeedbacks() + .setHint("Hint", "The hint") + .setCorrectSolution("Correct solution", "This is the correct solution") + .setCorrectFeedback("Correct feedback", "This is correct") + .setIncorrectFeedback("Incorrect", "Your answer is not correct") + .save(); + + //close editor + qtiPage + .clickToolbarBack(); + // access to all + qtiPage + .accessConfiguration() + .setUserAccess(UserAccess.guest) + .clickToolbarBack(); + // show results + qtiPage + .options() + .showResults(Boolean.TRUE, QTI21AssessmentResultsOptions.allOptions()) + .save(); + + //a user search the content package + LoginPage asukaLoginPage = LoginPage.getLoginPage(participantBrowser, deploymentUrl); + asukaLoginPage + .loginAs(asuka.getLogin(), asuka.getPassword()) + .resume(); + NavigationPage asukaNavBar = new NavigationPage(participantBrowser); + asukaNavBar + .openMyCourses() + .openSearch() + .extendedSearch(qtiTestTitle) + .select(qtiTestTitle) + .start(); + + // make the test + QTI21Page asukaQtiPage = QTI21Page + .getQTI12Page(participantBrowser); + asukaQtiPage + .assertOnAssessmentItem() + .answerMatchDropSourceToTarget("Einstein", "Chemistry") + .answerMatchDropSourceToTarget("Planck", "Philosophy") + .saveAnswer() + .assertFeedback("Incorrect") + .assertCorrectSolution("Correct solution") + .hint() + .assertFeedback("Hint") + .answerMatchDetarget("Planck") + .answerMatchDetarget("Einstein") + .saveAnswer() + .assertFeedback("Correct feedback") + .nextAnswer() + .answerMatchDropSourceToTarget("Broglie", "Astrology") // -0.5 points + .answerMatchDropSourceToTarget("Euler", "Chemistry") // 1 points + .answerMatchDropSourceToTarget("Konrad", "Chemistry") // 0 points + .saveAnswer() + .assertCorrectSolution("Correct solution") + .assertFeedback("Incorrect") + .endTest() + .assertOnAssessmentResults() + .assertOnAssessmentTestScore("4.5"); + + //a second user search the content package + LoginPage charaLoginPage = LoginPage.getLoginPage(participantBrowser, deploymentUrl); + charaLoginPage + .loginAs(chara.getLogin(), chara.getPassword()) + .resume(); + NavigationPage charaNavBar = new NavigationPage(participantBrowser); + charaNavBar + .openMyCourses() + .openSearch() + .extendedSearch(qtiTestTitle) + .select(qtiTestTitle) + .start(); + + // make the test + QTI21Page + .getQTI12Page(participantBrowser) + .saveAnswer() + .assertFeedback("Correct feedback") + .nextAnswer() + .answerMatchDropSourceToTarget("Broglie", "Chemistry") // 2 points + .answerMatchDropSourceToTarget("Euler", "Astrology") // 2 points + .answerMatchDropSourceToTarget("Konrad", "Astrology") // 3 points + .saveAnswer() + .assertCorrectSolution("Correct solution") + .assertFeedback("Incorrect") + .answerMatchDetarget("Broglie") + .answerMatchDetarget("Euler") + .answerMatchDetarget("Konrad") + .saveAnswer() + .endTest() + .assertOnAssessmentResults() + .assertOnAssessmentTestScore(12);// 4 points from the first question, 8 from the second + } + /** * An author make a test with 1 upload and feedbacks.<br> * A user make the test, test hint and upload the file. diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java index eaefc7615ac..7db9c628557 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java @@ -368,6 +368,12 @@ public class QTI21Page { return this; } + public QTI21Page assertOnAssessmentTestScore(String score) { + By resultsBy = By.xpath("//div[contains(@class,'o_sel_results_details')]//tr[contains(@class,'o_sel_assessmenttest_scores')]/td/div/span[contains(@class,'o_sel_assessmenttest_score')][contains(text(),'" + score + "')]"); + OOGraphene.waitElement(resultsBy, 5, browser); + return this; + } + public QTI21Page assertOnAssessmentTestPassed() { By notPassedBy = By.cssSelector("div.o_sel_results_details tr.o_qti_stateinfo.o_passed"); OOGraphene.waitElement(notPassedBy, 5, browser); -- GitLab