diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/MinimalScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/MinimalScoreController.java index a1039fb5c1920e3f58df03e044ac423d259f1e8d..d35e6e5d3c614b13f0d7abb4ad17d4d4196d4a21 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/MinimalScoreController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/MinimalScoreController.java @@ -62,12 +62,13 @@ public class MinimalScoreController extends AssessmentItemRefEditorController { setFormContextHelp(contextHelpUrl); super.initForm(formLayout, listener, ureq); minScoreEl = uifactory.addTextElement("min.score", "min.score", 8, "0.0", formLayout); + minScoreEl.setElementCssClass("o_sel_assessment_item_min_score"); minScoreEl.setEnabled(false); - minScoreEl.setEnabled(!restrictedEdit); ScoreBuilder maxScore = itemBuilder.getMaxScoreBuilder(); String maxValue = maxScore == null ? "" : (maxScore.getScore() == null ? "" : maxScore.getScore().toString()); maxScoreEl = uifactory.addTextElement("max.score", "max.score", 8, maxValue, formLayout); + maxScoreEl.setElementCssClass("o_sel_assessment_item_max_score"); maxScoreEl.setEnabled(!restrictedEdit); // Submit Button diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/KPrimEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/KPrimEditorController.java index c35cbc73cbd1de2c1d35876eed10a9644a8a0d54..d075f236618d93f3245f662c0d9d667dcf0e59bf 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/KPrimEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/KPrimEditorController.java @@ -142,6 +142,7 @@ public class KPrimEditorController extends FormBasicController { // Submit Button FormLayoutContainer buttonsContainer = FormLayoutContainer.createDefaultFormLayout("buttons", getTranslator()); + buttonsContainer.setElementCssClass("o_sel_choices_save"); buttonsContainer.setRootForm(mainForm); formLayout.add(buttonsContainer); formLayout.add("buttons", buttonsContainer); 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 3d09ec0b36d115eea7003e1c30b699ac7bfd458f..5329a1cc694b73fbdbf1e807ed3a34371a6c0f89 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 @@ -120,6 +120,7 @@ public class MatchEditorController extends FormBasicController { //single choice / multiple choice String[] singleMultiValues = new String[]{ translate("form.imd.match.single.choice"), translate("form.imd.match.multiple.choice") }; singleMultiEl = uifactory.addRadiosHorizontal("singleMulti", "form.imd.match.single.multiple", metadata, singleMultiKeys, singleMultiValues); + singleMultiEl.setElementCssClass("o_sel_match_single"); singleMultiEl.setEnabled(!restrictedEdit); singleMultiEl.addActionListener(FormEvent.ONCHANGE); if (itemBuilder.isMultipleChoice()) { @@ -160,8 +161,10 @@ public class MatchEditorController extends FormBasicController { uifactory.addFormSubmitButton("submit", answersCont); if(!restrictedEdit) { addColumnButton = uifactory.addFormLink("add.match.column", answersCont, Link.BUTTON); + addColumnButton.setElementCssClass("o_sel_match_add_column"); addColumnButton.setIconLeftCSS("o_icon o_icon_add"); addRowButton = uifactory.addFormLink("add.match.row", answersCont, Link.BUTTON); + addRowButton.setElementCssClass("o_sel_match_add_row"); addRowButton.setIconLeftCSS("o_icon o_icon_add"); } } diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/kprim_choices.html b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/kprim_choices.html index c4a4385900efa3fb17ecd360db732bdf5bb38376..27ffc45b9488d03c1ad9d769d935b1f68ffac7f1 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/kprim_choices.html +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/kprim_choices.html @@ -6,7 +6,7 @@ </div> #foreach($choice in $choices) -<div class="form-group #if($f.hasError($item)) has-feedback has-error #end clearfix"> +<div class="form-group #if($f.hasError($item)) has-feedback has-error #end clearfix o_sel_choice_${foreach.index}"> <div class="col-sm-1"> #if($r.available(${choice.getUp().getComponent().getComponentName()}) && $r.visible(${choice.getUp().getComponent().getComponentName()})) <div>$r.render(${choice.getUp().getComponent().getComponentName()})</div> diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_choices.html b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_choices.html index 957c6364e8a9928051dac8568562f479bed3f378..36bf895fcf76822c27ab3a3f08a1822398857adf 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_choices.html +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/_content/match_choices.html @@ -5,7 +5,7 @@ <tr> <th></th> #foreach($targetChoice in $targetChoices) - <th>$r.render(${targetChoice.getText()}) + <th class="o_sel_match_target_${foreach.index}">$r.render(${targetChoice.getText()}) #if($f.hasError($item)) <span class="o_icon o_icon_error form-control-feedback"></span> #end #if(!$restrictedEdit) <div class="pull-right">$r.render(${targetChoice.getDeleteButton()})</div> #end</th> @@ -16,16 +16,17 @@ #foreach($sourceChoice in $sourceChoices) #set($set1Identifier = $sourceChoice.getIdentifierString()) <tr> - <th>$r.render(${sourceChoice.getText()}) + <th class="o_sel_match_source_${foreach.index}">$r.render(${sourceChoice.getText()}) #if($f.hasError($item)) <span class="o_icon o_icon_error form-control-feedback"></span> #end #if(!$restrictedEdit) <div class="pull-right">$r.render(${sourceChoice.getDeleteButton()})</div> #end </th> + #set($sourceIndex = ${foreach.index}) #foreach($targetChoice in $targetChoices) #set($set2Identifier = $targetChoice.getIdentifierString()) #set($responseValue = $set1Identifier + " " + $set2Identifier) - <td> + <td class="o_sel_match_${sourceIndex}_${foreach.index}"> <input id="oo_${set1Identifier}_${set2Identifier}" type="checkbox" name="qtiworks_response_${responseIdentifier}" value="${responseValue}" #if(${sourceChoice.isCorrect(${targetChoice.getIdentifier()})}) checked #end #if($restrictedEdit) disabled #end/> $f.appendFlexiFormDirtyForCheckbox("oo_${set1Identifier}_${set2Identifier}") #if(${sourceChoice.isErrorSingleChoice()}) diff --git a/src/test/java/org/olat/selenium/ImsQTI21Test.java b/src/test/java/org/olat/selenium/ImsQTI21Test.java index 74bd73bd58e155304d094b87ba2912b4e9a54e82..43d21b273363868f25dead89085bc5bf68f1ac40 100644 --- a/src/test/java/org/olat/selenium/ImsQTI21Test.java +++ b/src/test/java/org/olat/selenium/ImsQTI21Test.java @@ -45,6 +45,7 @@ import org.olat.selenium.page.course.CourseEditorPageFragment; import org.olat.selenium.page.course.CoursePageFragment; import org.olat.selenium.page.qti.QTI21ConfigurationCEPage; import org.olat.selenium.page.qti.QTI21EditorPage; +import org.olat.selenium.page.qti.QTI21KprimEditorPage; import org.olat.selenium.page.qti.QTI21MultipleChoiceEditorPage; import org.olat.selenium.page.qti.QTI21Page; import org.olat.selenium.page.qti.QTI21SingleChoiceEditorPage; @@ -1249,4 +1250,175 @@ public class ImsQTI21Test { .assertOnAssessmentResults() .assertOnAssessmentTestScore(6);// 3 points from the first question, 3 from the second } + + + /** + * An author make a test with 2 kprims.<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 qti21EditorKprim(@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()); + + //upload a test + String qtiTestTitle = "Choices 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 kprim + QTI21KprimEditorPage kprimEditor = qtiEditor + .addKprim(); + kprimEditor + .setAnswer(0, "Correct") + .setCorrect(0, true) + .setAnswer(1, "OkToo") + .setCorrect(1, true) + .setAnswer(2, "Faux") + .setCorrect(2, false) + .setAnswer(3, "Falsch") + .setCorrect(3, false) + .save(); + // change max score + kprimEditor + .selectScores() + .setMaxScore("4") + .save(); + // set some feedbacks + kprimEditor + .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 kprim + kprimEditor = qtiEditor + .addKprim() + .setAnswer(0, "OnlyRight") + .setCorrect(0, true) + .setAnswer(1, "NotRight") + .setCorrect(1, false) + .setAnswer(2, "NotAnswer") + .setCorrect(2, false) + .setAnswer(3, "TheWrongOne") + .setCorrect(3, false) + .save(); + kprimEditor + .selectScores() + .setMaxScore("2") + .save(); + kprimEditor + .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() + .answerCorrectKPrim("Correct", "OkToo", "Faux") + .answerIncorrectKPrim("Falsch") + .saveAnswer() + .assertFeedback("Incorrect") + .assertCorrectSolution("Correct solution") + .hint() + .assertFeedback("Hint") + .answerCorrectKPrim("Correct", "OkToo") + .answerIncorrectKPrim("Falsch", "Faux") + .saveAnswer() + .assertFeedback("Correct feedback") + .nextAnswer() + .answerIncorrectKPrim("OnlyRight", "NotRight", "NotAnswer", "TheWrongOne") + .saveAnswer() + .assertCorrectSolution("Correct solution") + .assertFeedback("Incorrect") + .endTest() + .assertOnAssessmentResults() + .assertOnAssessmentTestScore(5);// 4 points from the first question, 1 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() + .answerCorrectKPrim("Correct", "OkToo") + .answerIncorrectKPrim("Faux", "Falsch") + .saveAnswer() + .assertFeedback("Correct feedback") + .nextAnswer() + .answerCorrectKPrim("OnlyRight") + .answerIncorrectKPrim("NotRight", "NotAnswer", "TheWrongOne") + .saveAnswer() + .endTest() + .assertOnAssessmentResults() + .assertOnAssessmentTestScore(6);// 3 points from the first question, 3 from the second + } } diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21EditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21EditorPage.java index 438bf39c0d5b718a2bb90865e40c57d984b8b89d..52327d460440c5534bd166dc71b7c1b4abfb0168 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21EditorPage.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21EditorPage.java @@ -93,6 +93,16 @@ public class QTI21EditorPage { return new QTI21MultipleChoiceEditorPage(browser); } + public QTI21KprimEditorPage addKprim() { + addQuestion(QTI21QuestionType.kprim); + return new QTI21KprimEditorPage(browser); + } + + public QTI21MatchEditorPage addMatch() { + addQuestion(QTI21QuestionType.match); + return new QTI21MatchEditorPage(browser); + } + private QTI21EditorPage addQuestion(QTI21QuestionType type) { openElementsMenu(); diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21KprimEditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21KprimEditorPage.java new file mode 100644 index 0000000000000000000000000000000000000000..9f3514b83d16780c727b6429dc980119ec9f96c2 --- /dev/null +++ b/src/test/java/org/olat/selenium/page/qti/QTI21KprimEditorPage.java @@ -0,0 +1,79 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.selenium.page.qti; + +import org.olat.selenium.page.graphene.OOGraphene; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +/** + * + * Initial date: 9 mai 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21KprimEditorPage extends QTI21AssessmentItemEditorPage { + + public QTI21KprimEditorPage(WebDriver browser) { + super(browser); + } + + /** + * Set if the answer is correct or wrong. + * + * @param position + * @param correct + * @return Itself + */ + public QTI21KprimEditorPage setCorrect(int position, boolean correct) { + By answerBy; + if(correct) { + answerBy = By.xpath("//div[contains(@class,'o_sel_choice_" + position + "')]//input[contains(@id,'oo_correct-')]"); + } else { + answerBy = By.xpath("//div[contains(@class,'o_sel_choice_" + position + "')]//input[contains(@id,'oo_wrong-')]"); + } + browser.findElement(answerBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + + public QTI21KprimEditorPage setAnswer(int position, String answer) { + String containerCssSelector = "div.o_sel_choice_" + position; + OOGraphene.tinymce(answer, containerCssSelector, browser); + return this; + } + + public QTI21KprimEditorPage save() { + By saveBy = By.cssSelector("fieldset.o_sel_choices_save button.btn.btn-primary"); + browser.findElement(saveBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + + public QTI21ChoicesScoreEditorPage selectScores() { + selectTab(By.className("o_sel_assessment_item_options")); + return new QTI21ChoicesScoreEditorPage(browser); + } + + public QTI21FeedbacksEditorPage selectFeedbacks() { + selectTab(By.className("o_sel_assessment_item_feedbacks")); + return new QTI21FeedbacksEditorPage(browser); + } +} diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21MatchEditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21MatchEditorPage.java new file mode 100644 index 0000000000000000000000000000000000000000..9057a0eeb6c1b3f5f8fff5bee2c839c2d26e1f59 --- /dev/null +++ b/src/test/java/org/olat/selenium/page/qti/QTI21MatchEditorPage.java @@ -0,0 +1,79 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.selenium.page.qti; + +import org.olat.selenium.page.graphene.OOGraphene; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +/** + * + * Initial date: 9 mai 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21MatchEditorPage extends QTI21AssessmentItemEditorPage { + + public QTI21MatchEditorPage(WebDriver browser) { + super(browser); + } + + /** + * Set if the answer is correct or wrong. + * + * @param position + * @param correct + * @return Itself + */ + public QTI21MatchEditorPage setCorrect(int position, boolean correct) { + By answerBy; + if(correct) { + answerBy = By.xpath("//div[contains(@class,'o_sel_choice_" + position + "')]//input[contains(@id,'oo_correct-')]"); + } else { + answerBy = By.xpath("//div[contains(@class,'o_sel_choice_" + position + "')]//input[contains(@id,'oo_wrong-')]"); + } + browser.findElement(answerBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + + public QTI21MatchEditorPage setAnswer(int position, String answer) { + String containerCssSelector = "div.o_sel_choice_" + position; + OOGraphene.tinymce(answer, containerCssSelector, browser); + return this; + } + + public QTI21MatchEditorPage save() { + By saveBy = By.cssSelector("fieldset.o_sel_choices_save button.btn.btn-primary"); + browser.findElement(saveBy).click(); + OOGraphene.waitBusy(browser); + return this; + } + + public QTI21ChoicesScoreEditorPage selectScores() { + selectTab(By.className("o_sel_assessment_item_options")); + return new QTI21ChoicesScoreEditorPage(browser); + } + + public QTI21FeedbacksEditorPage selectFeedbacks() { + selectTab(By.className("o_sel_assessment_item_feedbacks")); + return new QTI21FeedbacksEditorPage(browser); + } +} 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 efd63eede2949fe76cb562c11132fd2630a7bc8c..219959bdc142884a89d1e5e29b0d8d1f7cfd59a3 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java @@ -132,20 +132,28 @@ public class QTI21Page { return this; } - public QTI21Page answerCorrectKPrim(String... correctChoices) { - for(String correctChoice:correctChoices) { - By correctBy = By.xpath("//tr[td/p[contains(text(),'" + correctChoice + "')]]/td[contains(@class,'o_qti_item_kprim_input o_qti_item_kprim_input_correct')]/input[@type='checkbox']"); - WebElement option = browser.findElement(correctBy); - OOGraphene.check(option, Boolean.TRUE); + public QTI21Page answerCorrectKPrim(String... choices) { + for(String choice:choices) { + By incorrectBy = By.xpath("//tr[td/p[contains(text(),'" + choice + "')]]/td[contains(@class,'o_qti_item_kprim_input o_qti_item_kprim_input_wrong')]/input[@type='checkbox']"); + WebElement incorrectEl = browser.findElement(incorrectBy); + OOGraphene.check(incorrectEl, Boolean.FALSE); + + By correctBy = By.xpath("//tr[td/p[contains(text(),'" + choice + "')]]/td[contains(@class,'o_qti_item_kprim_input o_qti_item_kprim_input_correct')]/input[@type='checkbox']"); + WebElement correctEl = browser.findElement(correctBy); + OOGraphene.check(correctEl, Boolean.TRUE); } return this; } - public QTI21Page answerIncorrectKPrim(String... incorrectChoices) { - for(String incorrectChoice:incorrectChoices) { - By correctBy = By.xpath("//tr[td/p[contains(text(),'" + incorrectChoice + "')]]/td[contains(@class,'o_qti_item_kprim_input o_qti_item_kprim_input_wrong')]/input[@type='checkbox']"); - WebElement option = browser.findElement(correctBy); - OOGraphene.check(option, Boolean.TRUE); + public QTI21Page answerIncorrectKPrim(String... choices) { + for(String choice:choices) { + By correctBy = By.xpath("//tr[td/p[contains(text(),'" + choice + "')]]/td[contains(@class,'o_qti_item_kprim_input o_qti_item_kprim_input_correct')]/input[@type='checkbox']"); + WebElement correctEl = browser.findElement(correctBy); + OOGraphene.check(correctEl, Boolean.FALSE); + + By incorrectBy = By.xpath("//tr[td/p[contains(text(),'" + choice + "')]]/td[contains(@class,'o_qti_item_kprim_input o_qti_item_kprim_input_wrong')]/input[@type='checkbox']"); + WebElement incorrectEl = browser.findElement(incorrectBy); + OOGraphene.check(incorrectEl, Boolean.TRUE); } return this; }