From f0174c5125d57d8d17a2a3bf847d07055b522714 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 3 Nov 2017 20:47:36 +0100
Subject: [PATCH] no-jira: add 2 seleniums test for the numerical input editor

---
 .../FIBNumericalEntrySettingsController.java  |   6 +
 .../java/org/olat/selenium/ImsQTI21Test.java  | 345 +++++++++++++++++-
 .../selenium/page/qti/QTI21EditorPage.java    |   5 +
 .../page/qti/QTI21GapEntriesEditorPage.java   |  83 +++++
 4 files changed, 438 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBNumericalEntrySettingsController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBNumericalEntrySettingsController.java
index 4b338911918..96f165f1e2d 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBNumericalEntrySettingsController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBNumericalEntrySettingsController.java
@@ -78,9 +78,12 @@ public class FIBNumericalEntrySettingsController extends FormBasicController {
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		formLayout.setElementCssClass("o_sel_gap_numeric_form");
+		
 		Double solution = interaction.getSolution();
 		String solString = solution == null ? "" : solution.toString();
 		solutionEl = uifactory.addTextElement("fib.solution", "fib.solution", 256, solString, formLayout);
+		solutionEl.setElementCssClass("o_sel_gap_numeric_solution");
 		solutionEl.setEnabled(!restrictedEdit);
 		if(!restrictedEdit && !StringHelper.containsNonWhitespace(solString)) {
 			solutionEl.setFocus(true);
@@ -88,6 +91,7 @@ public class FIBNumericalEntrySettingsController extends FormBasicController {
 		
 		String placeholder = interaction.getPlaceholder();
 		placeholderEl = uifactory.addTextElement("fib.placeholder", "fib.placeholder", 256, placeholder, formLayout);
+		placeholderEl.setElementCssClass("o_sel_gap_numeric_placeholder");
 		placeholderEl.setEnabled(!restrictedEdit);
 		
 		Integer expectedLength = interaction.getExpectedLength();
@@ -131,6 +135,7 @@ public class FIBNumericalEntrySettingsController extends FormBasicController {
 		}
 		lowerToleranceEl = uifactory.addTextElement("fib.tolerance.low", "fib.tolerance.low", 8, lowerToleranceString, formLayout);
 		lowerToleranceEl.setExampleKey("fib.tolerance.mode.absolute.example", null);
+		lowerToleranceEl.setElementCssClass("o_sel_gap_numeric_lower_bound");
 		lowerToleranceEl.setEnabled(!restrictedEdit);
 		
 		Double upperTolerance = interaction.getUpperTolerance();
@@ -150,6 +155,7 @@ public class FIBNumericalEntrySettingsController extends FormBasicController {
 		}
 		upperToleranceEl = uifactory.addTextElement("fib.tolerance.up", "fib.tolerance.up", 8, upperToleranceString, formLayout);
 		upperToleranceEl.setExampleKey("fib.tolerance.mode.absolute.example", null);
+		upperToleranceEl.setElementCssClass("o_sel_gap_numeric_upper_bound");
 		upperToleranceEl.setEnabled(!restrictedEdit);
 		updateToleranceUpAndLow();
 
diff --git a/src/test/java/org/olat/selenium/ImsQTI21Test.java b/src/test/java/org/olat/selenium/ImsQTI21Test.java
index 3755cdfca5a..81c6773ce83 100644
--- a/src/test/java/org/olat/selenium/ImsQTI21Test.java
+++ b/src/test/java/org/olat/selenium/ImsQTI21Test.java
@@ -65,6 +65,7 @@ import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
 
+import uk.ac.ed.ph.jqtiplus.node.expression.operator.ToleranceMode;
 import uk.ac.ed.ph.jqtiplus.value.Cardinality;
 
 /**
@@ -1969,7 +1970,7 @@ public class ImsQTI21Test {
 		UserVO rei = new UserRestClient(deploymentUrl).createRandomUser("Rei");
 		authorLoginPage.loginAs(author.getLogin(), author.getPassword());
 
-		String qtiTestTitle = "Hotspot QTI 2.1 " + UUID.randomUUID();
+		String qtiTestTitle = "FIB QTI 2.1 " + UUID.randomUUID();
 		navBar
 			.openAuthoringEnvironment()
 			.createQTI21Test(qtiTestTitle)
@@ -2117,6 +2118,348 @@ public class ImsQTI21Test {
 			.assertOnAssessmentTestScore(6);// 2 points from the first question, 4 from the second
 	}
 	
+
+	/**
+	 * An author make a test with 2 questions using numerical input,
+	 * the first with the score set if all answers are correct, the second
+	 * with scoring per answers. The numerical input have all the tolerance
+	 * mode set to EXACT.<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 qti21EditorNumericalInput_exact(@InitialPage LoginPage authorLoginPage,
+			@Drone @User WebDriver participantBrowser)
+	throws IOException, URISyntaxException {
+
+		UserVO author = new UserRestClient(deploymentUrl).createAuthor();
+		UserVO ryomou = new UserRestClient(deploymentUrl).createRandomUser("Ryomou");
+		UserVO rei = new UserRestClient(deploymentUrl).createRandomUser("Rei");
+		authorLoginPage.loginAs(author.getLogin(), author.getPassword());
+
+		String qtiTestTitle = "Numerical 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 numerical input: all answers score, tolerance exact
+		QTI21GapEntriesEditorPage fibEditor = qtiEditor
+			.addNumerical()
+			.appendContent("One plus two: ")
+			.addNumericalInput("3", "three", ToleranceMode.EXACT, null, null)
+			.saveNumericInput()
+			.editNumericalInput("9", "nine", ToleranceMode.EXACT, null, null, 2)
+			.saveNumericInput()
+			.save();
+		//set max score
+		fibEditor
+			.selectScores()
+			.selectAssessmentMode(ScoreEvaluation.allCorrectAnswers)
+			.setMaxScore("2")
+			.save();
+		// set feedbacks
+		fibEditor
+			.selectFeedbacks()
+			.setHint("Hint", "The second is the first power two")
+			.setCorrectSolution("Correct solution", "I know you know")
+			.setCorrectFeedback("Correct feedback", "Your answer is correct")
+			.setIncorrectFeedback("Incorrect", "Your answer is not correct")
+			.save();
+		
+		//add a gap entry: score per answer, tolerance exact
+		fibEditor = qtiEditor
+			.addNumerical()
+			.appendContent("More difficult: 34 + 23 ")
+			.addNumericalInput("57", "57", ToleranceMode.EXACT, null, null)
+			.saveNumericInput()
+			.editNumericalInput("8", "64squareroot",ToleranceMode.EXACT, null, null, 2)
+			.saveNumericInput()
+			.save();
+		//set max score
+		fibEditor
+			.selectScores()
+			.selectAssessmentMode(ScoreEvaluation.perAnswer)
+			.setMaxScore("4")
+			.setScore("57", "2")
+			.setScore("8", "3")
+			.save();
+		// set feedbacks
+		fibEditor
+			.selectFeedbacks()
+			.setHint("Hint", "The second is the square root of 64")
+			.setCorrectSolution("Correct solution", "This is an information about the correct solution")
+			.setCorrectFeedback("Correct feedback", "Your answer 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 userLoginPage = LoginPage.getLoginPage(participantBrowser, deploymentUrl);
+		userLoginPage
+			.loginAs(ryomou.getLogin(), ryomou.getPassword())
+			.resume();
+		NavigationPage userNavBar = new NavigationPage(participantBrowser);
+		userNavBar
+			.openMyCourses()
+			.openSearch()
+			.extendedSearch(qtiTestTitle)
+			.select(qtiTestTitle)
+			.start();
+		
+		// first user make the test
+		QTI21Page ryomouQtiPage = QTI21Page
+				.getQTI12Page(participantBrowser);
+		ryomouQtiPage
+			.assertOnAssessmentItem()
+			.answerGapTextWithPlaceholder("2", "three")
+			.answerGapTextWithPlaceholder("25", "nine")
+			.saveAnswer()
+			.assertFeedback("Incorrect")
+			.assertCorrectSolution("Correct solution")
+			.hint()
+			.assertFeedback("Hint")
+			.answerGapTextWithPlaceholder("3", "three")
+			.answerGapTextWithPlaceholder("9", "nine")
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.nextAnswer()
+			.answerGapTextWithPlaceholder("57", "57")
+			.answerGapTextWithPlaceholder("9", "64squareroot")
+			.saveAnswer()
+			.assertCorrectSolution("Correct solution")
+			.assertFeedback("Incorrect")
+			.endTest()
+			.assertOnAssessmentResults()
+			.assertOnAssessmentTestScore(4);// 2 points from the first question, 4 from the second
+		
+
+		//a second 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 with all the correct answers
+		QTI21Page
+			.getQTI12Page(participantBrowser)
+			.assertOnAssessmentItem()
+			.answerGapTextWithPlaceholder("3", "three")
+			.answerGapTextWithPlaceholder("9", "nine")
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.nextAnswer()
+			.answerGapTextWithPlaceholder("57", "57")
+			.answerGapTextWithPlaceholder("8", "64squareroot")
+			.saveAnswer()
+			.endTest()
+			.assertOnAssessmentResults()
+			.assertOnAssessmentTestScore(6);// 2 points from the first question, 4 from the second
+	}
+
+	/**
+	 * An author make a test with 2 questions using numerical input to
+	 * test the absolute tolerance mode.<br>
+	 * A first user make the test, but doesn't answer all questions
+	 * correctly, log out and a second user make the perfect test but
+	 * on the limit.
+	 * 
+	 * @param authorLoginPage
+	 * @param participantBrowser
+	 * @throws IOException
+	 * @throws URISyntaxException
+	 */
+	@Test
+	@RunAsClient
+	public void qti21EditorNumericalInput_absolut(@InitialPage LoginPage authorLoginPage,
+			@Drone @User WebDriver participantBrowser)
+	throws IOException, URISyntaxException {
+
+		UserVO author = new UserRestClient(deploymentUrl).createAuthor();
+		UserVO ryomou = new UserRestClient(deploymentUrl).createRandomUser("Ryomou");
+		UserVO rei = new UserRestClient(deploymentUrl).createRandomUser("Rei");
+		authorLoginPage.loginAs(author.getLogin(), author.getPassword());
+
+		String qtiTestTitle = "Numerical 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 numerical input: 3.1 - 3.2
+		QTI21GapEntriesEditorPage fibEditor = qtiEditor
+			.addNumerical()
+			.appendContent("Usefull for circles ")
+			.editNumericalInput("3.1416", "pi", ToleranceMode.ABSOLUTE, "3.2", "3.1", 1)
+			.saveNumericInput()
+			.save();
+		// use standard score setting
+		// set feedbacks
+		fibEditor
+			.selectFeedbacks()
+			.setCorrectFeedback("Correct feedback", "Your answer is correct")
+			.setIncorrectFeedback("Incorrect", "Out of bounds")
+			.save();
+		
+		//add a numerical input which represent a rounding issue
+		fibEditor = qtiEditor
+			.addNumerical()
+			.appendContent("Check rounding issue ")
+			.editNumericalInput("14.923", "rounding", ToleranceMode.ABSOLUTE, "14.925", "14.915", 1)
+			.saveNumericInput()
+			.save();
+		// set feedbacks
+		fibEditor
+			.selectFeedbacks()
+			.setCorrectFeedback("Correct feedback", "Your answer is correct")
+			.setIncorrectFeedback("Incorrect", "Your answer is not correct")
+			.save();
+		
+		//add a numerical input with negative values
+		fibEditor = qtiEditor
+			.addNumerical()
+			.appendContent("Check rounding issue ")
+			.editNumericalInput("-14.923", "negative", ToleranceMode.ABSOLUTE, "-14.921", "-14.931", 1)
+			.saveNumericInput()
+			.save();
+		// set feedbacks
+		fibEditor
+			.selectFeedbacks()
+			.setCorrectFeedback("Correct feedback", "Your answer 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 userLoginPage = LoginPage.getLoginPage(participantBrowser, deploymentUrl);
+		userLoginPage
+			.loginAs(ryomou.getLogin(), ryomou.getPassword())
+			.resume();
+		NavigationPage userNavBar = new NavigationPage(participantBrowser);
+		userNavBar
+			.openMyCourses()
+			.openSearch()
+			.extendedSearch(qtiTestTitle)
+			.select(qtiTestTitle)
+			.start();
+		
+		// first user make the test
+		QTI21Page ryomouQtiPage = QTI21Page
+				.getQTI12Page(participantBrowser);
+		ryomouQtiPage
+			.assertOnAssessmentItem()
+			.answerGapTextWithPlaceholder("3", "pi")
+			.saveAnswer()
+			.assertFeedback("Incorrect")
+			.answerGapTextWithPlaceholder("3.15", "pi")
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.nextAnswer()
+			.answerGapTextWithPlaceholder("14.914", "rounding")
+			.saveAnswer()
+			.assertFeedback("Incorrect")
+			.answerGapTextWithPlaceholder("14.915", "rounding")
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.nextAnswer()
+			.answerGapTextWithPlaceholder("-14.932", "negative")
+			.saveAnswer()
+			.assertFeedback("Incorrect")
+			.answerGapTextWithPlaceholder("-14.920", "negative")
+			.saveAnswer()
+			.endTest()
+			.assertOnAssessmentResults()
+			.assertOnAssessmentTestScore(2);// 1 point + 1 point + 0 point
+		
+
+		//a second 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 with all the correct answers
+		QTI21Page
+			.getQTI12Page(participantBrowser)
+			.assertOnAssessmentItem()
+			.answerGapTextWithPlaceholder("3.2", "pi")
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.nextAnswer()
+			.answerGapTextWithPlaceholder("14.925", "rounding")
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.nextAnswer()
+			.answerGapTextWithPlaceholder("-14.921", "negative")
+			.saveAnswer()
+			.assertFeedback("Correct feedback")
+			.endTest()
+			.assertOnAssessmentResults()
+			.assertOnAssessmentTestScore(3); 
+	}
+	
 	/**
 	 * An author make a test with 2 matches. A match with "multiple selection"
 	 * and score "all answers", a second with "single selection" and score
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 da88b562678..9559fcdca35 100644
--- a/src/test/java/org/olat/selenium/page/qti/QTI21EditorPage.java
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21EditorPage.java
@@ -147,6 +147,11 @@ public class QTI21EditorPage {
 		return new QTI21GapEntriesEditorPage(browser);
 	}
 	
+	public QTI21GapEntriesEditorPage addNumerical() {
+		addQuestion(QTI21QuestionType.numerical);
+		return new QTI21GapEntriesEditorPage(browser);
+	}
+	
 	private QTI21EditorPage addQuestion(QTI21QuestionType type) {
 		openElementsMenu();
 		
diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21GapEntriesEditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21GapEntriesEditorPage.java
index 8213ce301d1..9737e0ae266 100644
--- a/src/test/java/org/olat/selenium/page/qti/QTI21GapEntriesEditorPage.java
+++ b/src/test/java/org/olat/selenium/page/qti/QTI21GapEntriesEditorPage.java
@@ -23,6 +23,9 @@ import org.olat.selenium.page.graphene.OOGraphene;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.Select;
+
+import uk.ac.ed.ph.jqtiplus.node.expression.operator.ToleranceMode;
 
 /**
  * 
@@ -106,6 +109,86 @@ public class QTI21GapEntriesEditorPage extends QTI21AssessmentItemEditorPage {
 		return this;
 	}
 	
+	/**
+	 * Add a new numerical input. Use the placeholder to locate
+	 * the gap during the test.
+	 * 
+	 * @param solution The solution
+	 * @param placeholder The placeholder
+	 * @return Itself
+	 */
+	public QTI21GapEntriesEditorPage addNumericalInput(String solution, String placeholder,
+			ToleranceMode toleranceMode, String uperrBound, String lowerBound) {
+		By addGapBy = By.xpath("//div[contains(@class,'o_sel_assessment_item_fib_text')]//button[i[contains(@class,'mce-i-gapnumerical')]]");
+		browser.findElement(addGapBy).click();
+		OOGraphene.waitModalDialog(browser);
+		return fillNumericalInputSettings(solution, placeholder, toleranceMode, uperrBound, lowerBound);
+	}
+	
+	/**
+	 * Edit an existing numerical input of type text. Use the placeholder to locate
+	 * the gap during the test.
+	 * 
+	 * @param solution The solution
+	 * @param placeholder The placeholder
+	 * @param index The index of the entry in the paragraph
+	 * @return Itself
+	 */
+	public QTI21GapEntriesEditorPage editNumericalInput(String solution, String placeholder,
+			ToleranceMode toleranceMode, String uperrBound, String lowerBound, int index) {
+		By frameBy = By.cssSelector("div.o_sel_assessment_item_fib_text div.mce-edit-area iframe");
+		WebElement frameEl = browser.findElement(frameBy);
+		browser.switchTo().frame(frameEl);
+		
+		By gapEntryBy = By.xpath("//p/span[@class='textentryinteraction'][" + index + "]/a");
+		browser.findElement(gapEntryBy).click();
+		
+		browser.switchTo().defaultContent();
+		OOGraphene.waitModalDialog(browser);
+		return fillNumericalInputSettings(solution, placeholder, toleranceMode, uperrBound, lowerBound);
+	}
+	
+	private QTI21GapEntriesEditorPage fillNumericalInputSettings(String solution, String placeholder,
+			ToleranceMode toleranceMode, String upperBound, String lowerBound) {
+		By solutionBy = By.cssSelector("fieldset.o_sel_gap_numeric_form div.o_sel_gap_numeric_solution input[type=text]");
+		WebElement solutionEl = browser.findElement(solutionBy);
+		solutionEl.clear();
+		solutionEl.sendKeys(solution);
+		
+		By placeholderBy = By.cssSelector("fieldset.o_sel_gap_numeric_form div.o_sel_gap_numeric_placeholder input[type=text]");
+		browser.findElement(placeholderBy).sendKeys(placeholder);
+		
+		By toleranceModeBy = By.xpath("//select[contains(@id,'fib_tolerance_mode')]");
+		WebElement toleranceModeEl = browser.findElement(toleranceModeBy);
+		new Select(toleranceModeEl).selectByValue(toleranceMode.name());
+		OOGraphene.waitBusy(browser);
+		
+		By upperBoundBy = By.cssSelector("fieldset.o_sel_gap_numeric_form div.o_sel_gap_numeric_upper_bound input[type=text]");
+		if(toleranceMode == ToleranceMode.EXACT) {
+			OOGraphene.waitElementDisappears(upperBoundBy, 5, browser);
+		} else {
+			OOGraphene.waitElement(upperBoundBy, browser);
+			
+			browser.findElement(upperBoundBy).sendKeys(upperBound);
+			By lowerBoundBy = By.cssSelector("fieldset.o_sel_gap_numeric_form div.o_sel_gap_numeric_lower_bound input[type=text]");
+			browser.findElement(lowerBoundBy).sendKeys(lowerBound);
+		}
+		return this;
+	}
+	
+	/**
+	 * Save and close the modal dialog to edit the numerical input.
+	 * 
+	 * @return Itself
+	 */
+	public QTI21GapEntriesEditorPage saveNumericInput() {
+		By saveBy = By.cssSelector("fieldset.o_sel_gap_numeric_form button.btn-primary");
+		browser.findElement(saveBy).click();
+		OOGraphene.waitBusy(browser);
+		return this;
+	}
+
+	
 	/**
 	 * Save the whole interaction.
 	 * 
-- 
GitLab