From 17398742b00c45a670582a3f6e34c9268fc90db3 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Tue, 29 Sep 2015 16:45:59 +0200
Subject: [PATCH] OO-1635: fix test course element and other things, integrate
 qtiworks unit tests to openolat

---
 pom.xml                                       |  60 +++++++++
 .../nodes/iq/IQConfigurationController.java   |  43 ++++---
 .../course/nodes/iq/IQEditController.java     |   1 +
 .../handlers/QTI21AssessmentTestHandler.java  |   7 +-
 .../ui/AssessmentItemDisplayController.java   |  25 +++-
 .../org/olat/ims/qti21/ui/_content/run.html   |   2 +-
 .../AssessmentItemComponentRenderer.java      |  12 +-
 .../AssessmentObjectComponentRenderer.java    |  43 +++++++
 .../AssessmentTestComponentRenderer.java      |  48 +------
 .../help/spi/ConfluenceLinkSPITest.java       |  20 +--
 .../java/org/olat/test/QtiWorksTests.java     | 119 ++++++++++++++++++
 11 files changed, 297 insertions(+), 83 deletions(-)
 create mode 100644 src/test/java/org/olat/test/QtiWorksTests.java

diff --git a/pom.xml b/pom.xml
index 82cd17f2519..20aa0efc690 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1267,6 +1267,7 @@
 					<testNGArtifactName>none:none</testNGArtifactName>
 					<includes>
 						<include>org/olat/test/AllTestsJunit4.java</include>
+						<include>org/olat/test/QtiWorksTests.java</include>
 					</includes>
 				</configuration>
 			</plugin>
@@ -1833,6 +1834,7 @@
 			<artifactId>reload-moonunit</artifactId>
 			<version>reload_dist255-moonunit.jar</version>
 		</dependency>
+		<!-- Sart dependency to qtiworks -->
 		<dependency>
 			<groupId>uk.ac.ed.ph.qtiworks</groupId>
 			<artifactId>qtiworks-jqtiplus</artifactId>
@@ -1844,6 +1846,19 @@
 				</exclusion>
 			</exclusions>
 		</dependency>
+		<dependency>
+			<groupId>uk.ac.ed.ph.qtiworks</groupId>
+			<artifactId>qtiworks-jqtiplus</artifactId>
+			<version>${qtiworks.version}</version>
+			<type>test-jar</type>
+			<scope>test</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>ch.qos.logback</groupId>
+					<artifactId>logback-classic</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
 		<dependency>
 			<groupId>uk.ac.ed.ph.qtiworks</groupId>
 			<artifactId>qtiworks-mathassess</artifactId>
@@ -1866,6 +1881,45 @@
 				</exclusion>
 			</exclusions>
 		</dependency>
+		<dependency>
+			<groupId>uk.ac.ed.ph.qtiworks</groupId>
+			<artifactId>qtiworks-mathassess-glue</artifactId>
+			<version>${qtiworks.version}</version>
+			<type>test-jar</type>
+			<scope>test</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>ch.qos.logback</groupId>
+					<artifactId>logback-classic</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>uk.ac.ed.ph.qtiworks</groupId>
+			<artifactId>qtiworks-samples</artifactId>
+			<version>${qtiworks.version}</version>
+			<scope>test</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>ch.qos.logback</groupId>
+					<artifactId>logback-classic</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>uk.ac.ed.ph.qtiworks</groupId>
+			<artifactId>qtiworks-samples</artifactId>
+			<version>${qtiworks.version}</version>
+			<type>test-jar</type>
+			<scope>test</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>ch.qos.logback</groupId>
+					<artifactId>logback-classic</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<!-- End dependency to qtiworks -->
 		<dependency>
 			<groupId>org.openolat.imscp</groupId>
 			<artifactId>manifest</artifactId>
@@ -2280,6 +2334,12 @@
 			<version>4.12</version>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>xmlunit</groupId>
+			<artifactId>xmlunit</artifactId>
+			<version>1.5</version>
+			<scope>test</scope>
+		</dependency>
 		<dependency>
 		    <groupId>io.undertow</groupId>
 		    <artifactId>undertow-core</artifactId>
diff --git a/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java b/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
index aaaf4db3285..8caea941c15 100644
--- a/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
+++ b/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
@@ -385,7 +385,7 @@ public class IQConfigurationController extends BasicController {
 					SurveyFileResource.TYPE_NAME, translate("command.chooseSurvey"));
 		} else { // test and selftest use same repository resource type
 			searchController = new ReferencableEntriesSearchController(getWindowControl(), ureq, 
-					TestFileResource.TYPE_NAME, translate("command.chooseTest"));
+					new String[] { TestFileResource.TYPE_NAME, ImsQTI21Resource.TYPE_NAME }, translate("command.chooseTest"));
 		}			
 		listenTo(searchController);
 		cmc = new CloseableModalController(getWindowControl(), translate("close"), searchController.getInitialComponent(), true, translate("command.chooseRepFile"));
@@ -501,22 +501,26 @@ public class IQConfigurationController extends BasicController {
 
 	private void checkEssay(RepositoryEntry re) {
 		if(OnyxModule.isOnyxTest(re.getOlatResource())) return;
-		if(courseNode instanceof IQSURVCourseNode || courseNode instanceof IQSELFCourseNode) return;
-		
-		TestFileResource fr = new TestFileResource();
-		fr.overrideResourceableId(re.getOlatResource().getResourceableId());
-		QTIEditorPackage qtiPackage = new QTIEditorPackageImpl(getIdentity(), fr, null, getTranslator());
-		Assessment ass = qtiPackage.getQTIDocument().getAssessment();
-
-		//Sections with their Items
-		List<Section> sections = ass.getSections();
-		for (Section section:sections) {
-			List<Item> items = section.getItems();
-			for (Item item:items) {
-				String ident = item.getIdent();
-				if(ident != null && ident.startsWith("QTIEDIT:ESSAY")) {
-					showWarning("warning.test.with.essay");
-					break;
+		if(courseNode instanceof IQSURVCourseNode || courseNode instanceof IQSELFCourseNode) {
+			//nothing to do
+		} else if(ImsQTI21Resource.TYPE_NAME.equals(re.getOlatResource().getResourceableTypeName())) {
+			//TODO qti ()	
+		} else {
+			TestFileResource fr = new TestFileResource();
+			fr.overrideResourceableId(re.getOlatResource().getResourceableId());
+			QTIEditorPackage qtiPackage = new QTIEditorPackageImpl(getIdentity(), fr, null, getTranslator());
+			Assessment ass = qtiPackage.getQTIDocument().getAssessment();
+	
+			//Sections with their Items
+			List<Section> sections = ass.getSections();
+			for (Section section:sections) {
+				List<Item> items = section.getItems();
+				for (Item item:items) {
+					String ident = item.getIdent();
+					if(ident != null && ident.startsWith("QTIEDIT:ESSAY")) {
+						showWarning("warning.test.with.essay");
+						break;
+					}
 				}
 			}
 		}
@@ -554,6 +558,11 @@ public class IQConfigurationController extends BasicController {
 					myContent.contextPut("onyxDisplayName", displayName);
 					moduleConfiguration.set(IQEditController.CONFIG_KEY_TYPE_QTI, IQEditController.CONFIG_VALUE_QTI2);
 					setOnyxVariables(re);
+				} else if(ImsQTI21Resource.TYPE_NAME.equals(re.getOlatResource().getResourceableTypeName())) {
+					//TODO qti
+					myContent.contextPut("showOutcomes", Boolean.FALSE);
+					moduleConfiguration.set(IQEditController.CONFIG_KEY_TYPE_QTI, IQEditController.CONFIG_VALUE_QTI21);
+					
 				} else {
 					myContent.contextPut("showOutcomes", Boolean.FALSE);
 					moduleConfiguration.set(IQEditController.CONFIG_KEY_TYPE_QTI, IQEditController.CONFIG_VALUE_QTI1);
diff --git a/src/main/java/org/olat/course/nodes/iq/IQEditController.java b/src/main/java/org/olat/course/nodes/iq/IQEditController.java
index 04dd24ab2b3..d326580fd70 100644
--- a/src/main/java/org/olat/course/nodes/iq/IQEditController.java
+++ b/src/main/java/org/olat/course/nodes/iq/IQEditController.java
@@ -130,6 +130,7 @@ public class IQEditController extends ActivateableTabbableDefaultController impl
 	public static final String CONFIG_KEY_TEMPLATE = "templateid";
 	public static final String CONFIG_KEY_TYPE_QTI = "qtitype";
 	public static final String CONFIG_VALUE_QTI2 = "qti2";
+	public static final String CONFIG_VALUE_QTI21 = "qti2w";
 	public static final Object CONFIG_VALUE_QTI1 = "qti1";
 	public static final String CONFIG_KEY_IS_SURVEY = "issurv";
 
diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
index 6ef22732deb..b1d5c40c88a 100644
--- a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
+++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
@@ -58,6 +58,7 @@ import org.olat.core.util.PathUtils.YesMatcher;
 import org.olat.core.util.coordinate.LockResult;
 import org.olat.course.assessment.AssessmentMode;
 import org.olat.fileresource.FileResourceManager;
+import org.olat.fileresource.ZippedDirectoryMediaResource;
 import org.olat.fileresource.types.FileResource;
 import org.olat.fileresource.types.ImsQTI21Resource;
 import org.olat.fileresource.types.ResourceEvaluation;
@@ -72,6 +73,7 @@ import org.olat.ims.qti21.ui.QTI21RuntimeController;
 import org.olat.ims.qti21.ui.editor.AssessmentTestComposerController;
 import org.olat.imscp.xml.manifest.ManifestType;
 import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
 import org.olat.repository.RepositoryService;
 import org.olat.repository.handlers.EditionSupport;
 import org.olat.repository.handlers.FileHandler;
@@ -274,7 +276,10 @@ public class QTI21AssessmentTestHandler extends FileHandler {
 
 	@Override
 	public MediaResource getAsMediaResource(OLATResourceable res, boolean backwardsCompatible) {
-		return null;
+		File unzippedDir = FileResourceManager.getInstance().unzipFileResource(res);
+		String displayName = CoreSpringFactory.getImpl(RepositoryManager.class)
+				.lookupDisplayNameByOLATResourceableId(res.getResourceableId());
+		return new ZippedDirectoryMediaResource(displayName, unzippedDir);
 	}
 	
 	@Override
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
index 89b59ccb537..0e6d4763b29 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
@@ -164,9 +164,9 @@ public class AssessmentItemDisplayController extends BasicController implements
 	
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(this.qtiWorksCtrl == source) {
+		if(qtiWorksCtrl == source) {
 			if(event instanceof QTIWorksAssessmentItemEvent) {
-				this.processQTIEvent(ureq, (QTIWorksAssessmentItemEvent)event);
+				processQTIEvent(ureq, (QTIWorksAssessmentItemEvent)event);
 			}
 		}
 		super.event(ureq, source, event);
@@ -192,9 +192,26 @@ public class AssessmentItemDisplayController extends BasicController implements
 			case exit:
 				exitSession(ureq);
 				break;
-		
+			case resetsoft:
+				break;
+			case resethard:
+				break;
+			case source:
+				logError("QtiWorks event source not implemented", null);
+				break;
+			case state:
+				logError("QtiWorks event state not implemented", null);
+				break;
+			case validation:
+				logError("QtiWorks event validation not implemented", null);
+				break;
+			case authorview:
+				logError("QtiWorks event authorview not implemented", null);
+				break;
+			case result:
+				logError("QtiWorks event result not implemented", null);
+				break;
 		}
-		
 	}
 	
 	private ItemSessionController enterSession(UserRequest ureq /*, final UserTestSession candidateSession */) {
diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/run.html b/src/main/java/org/olat/ims/qti21/ui/_content/run.html
index 3c89670ed09..12abd3085e5 100644
--- a/src/main/java/org/olat/ims/qti21/ui/_content/run.html
+++ b/src/main/java/org/olat/ims/qti21/ui/_content/run.html
@@ -1 +1 @@
-$r.render("qtirun")
\ No newline at end of file
+<div id="o_qti_run">$r.render("qtirun")</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
index ed367110150..786054e4775 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
@@ -22,8 +22,6 @@ package org.olat.ims.qti21.ui.components;
 import java.util.Date;
 
 import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.form.flexible.impl.FormEvent;
-import org.olat.core.gui.components.form.flexible.impl.FormJSHelper;
 import org.olat.core.gui.render.RenderResult;
 import org.olat.core.gui.render.Renderer;
 import org.olat.core.gui.render.StringOutput;
@@ -167,19 +165,21 @@ public class AssessmentItemComponentRenderer extends AssessmentObjectComponentRe
 		sb.append("<div id='itemBody'>");
 		
 		//TODO prompt
+	
 
 		//render itemBody
 		assessmentItem.getItemBody().getBlocks().forEach((block)
 				-> renderBlock(renderer, sb, component, assessmentItem, itemSessionState, block, ubu, translator));
 
 		//comment
-		
+		renderComment(renderer, sb, component, itemSessionState, translator);
+				
 		//submit button
 		if(component.isItemSessionOpen(itemSessionState, renderer.isSolutionMode())) {
-			sb.append("<button type='button' name='cid' value='response' class='btn btn-primary' ");
-			sb.append(FormJSHelper.getRawJSFor(component.getQtiItem().getRootForm(), component.getQtiItem().getFormDispatchId(), FormEvent.ONCLICK));
-			sb.append("><span>Submit</span></button>");
+			Component submit = component.getQtiItem().getSubmitButton().getComponent();
+			submit.getHTMLRendererSingleton().render(renderer.getRenderer(), sb, submit, ubu, translator, new RenderResult(), null);
 		}
+
 		//end body
 		sb.append("</div>");
 
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
index ce17515733c..221a8a01938 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponentRenderer.java
@@ -411,6 +411,49 @@ public abstract class AssessmentObjectComponentRenderer extends DefaultComponent
 		sb.append("</").append(node.getQtiClassName()).append(">");
 	}
 	
+  /*
+  <xsl:choose>
+    <xsl:when test="$allowComment and $isItemSessionOpen">
+      <fieldset class="candidateComment">
+        <legend>Please use the following text box if you need to provide any additional information, comments or feedback during this test:</legend>
+        <input name="qtiworks_comment_presented" type="hidden" value="true"/>
+        <textarea name="qtiworks_comment"><xsl:value-of select="$itemSessionState/qw:candidateComment"/></textarea>
+      </fieldset>
+    </xsl:when>
+    <xsl:when test="$allowComment and $isItemSessionEnded and exists($itemSessionState/qw:candidateComment)">
+      <fieldset class="candidateComment">
+        <legend>You submitted the folllowing comment with this item:</legend>
+        <input name="qtiworks_comment_presented" type="hidden" value="true"/>
+        <textarea name="qtiworks_comments" disabled="disabled"><xsl:value-of select="$itemSessionState/qw:candidateComment"/></textarea>
+      </fieldset>
+    </xsl:when>
+  </xsl:choose>
+  */
+	protected void renderComment(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, ItemSessionState itemSessionState, Translator translator) {
+		if(renderer.isCandidateCommentAllowed()) {
+			if(component.isItemSessionOpen(itemSessionState, renderer.isSolutionMode())) {
+				String comment = itemSessionState.getCandidateComment();
+				renderComment(sb, comment, false, translator);
+			} else if(component.isItemSessionEnded(itemSessionState, renderer.isSolutionMode())
+					&& StringHelper.containsNonWhitespace(itemSessionState.getCandidateComment())) {
+				String comment = itemSessionState.getCandidateComment();
+				renderComment(sb, comment, true, translator);
+			}
+		}
+		
+	}
+	
+	private void renderComment(StringOutput sb, String comment, boolean disabled, Translator translator) {
+		sb.append("<fieldset class='candidateComment'>")
+		  .append("<legend>").append(translator.translate("assessment.comment.legend")).append("</legend>")
+		  .append("<input name='qtiworks_comment_presented' type='hidden' value='true' />")
+		  .append("<textarea name='qtiworks_comment'").append(" disabled=\"disabled\"", disabled).append(">");
+		if(StringHelper.containsNonWhitespace(comment)) {
+			sb.append(comment);
+		}
+		sb.append("</textarea></fieldset>");
+	}
+	
 	private void renderEndAttemptInteraction(AssessmentRenderer renderer, StringOutput sb, EndAttemptInteraction interaction,
 			AssessmentObjectComponent component, URLBuilder ubu, Translator translator) {
 		
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
index 7b82d531f20..a1b7c0d079b 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
@@ -318,49 +318,7 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe
 			super.renderItemStatus(sb, itemSessionState);
 		}
 	}
-	
-  /*
-  <xsl:choose>
-    <xsl:when test="$allowComment and $isItemSessionOpen">
-      <fieldset class="candidateComment">
-        <legend>Please use the following text box if you need to provide any additional information, comments or feedback during this test:</legend>
-        <input name="qtiworks_comment_presented" type="hidden" value="true"/>
-        <textarea name="qtiworks_comment"><xsl:value-of select="$itemSessionState/qw:candidateComment"/></textarea>
-      </fieldset>
-    </xsl:when>
-    <xsl:when test="$allowComment and $isItemSessionEnded and exists($itemSessionState/qw:candidateComment)">
-      <fieldset class="candidateComment">
-        <legend>You submitted the folllowing comment with this item:</legend>
-        <input name="qtiworks_comment_presented" type="hidden" value="true"/>
-        <textarea name="qtiworks_comments" disabled="disabled"><xsl:value-of select="$itemSessionState/qw:candidateComment"/></textarea>
-      </fieldset>
-    </xsl:when>
-  </xsl:choose>
-  */
-	private void renderComment(AssessmentRenderer renderer, StringOutput sb, AssessmentTestComponent component, ItemSessionState itemSessionState, Translator translator) {
-		if(renderer.isCandidateCommentAllowed()) {
-			if(component.isItemSessionOpen(itemSessionState, renderer.isSolutionMode())) {
-				String comment = itemSessionState.getCandidateComment();
-				renderComment(sb, comment, false, translator);
-			} else if(component.isItemSessionEnded(itemSessionState, renderer.isSolutionMode())
-					&& StringHelper.containsNonWhitespace(itemSessionState.getCandidateComment())) {
-				String comment = itemSessionState.getCandidateComment();
-				renderComment(sb, comment, true, translator);
-			}
-		}
-		
-	}
-	
-	private void renderComment(StringOutput sb, String comment, boolean disabled, Translator translator) {
-		sb.append("<fieldset class='candidateComment'>")
-		  .append("<legend>").append(translator.translate("assessment.comment.legend")).append("</legend>")
-		  .append("<input name='qtiworks_comment_presented' type='hidden' value='true' />")
-		  .append("<textarea name='qtiworks_comment'").append(" disabled=\"disabled\"", disabled).append(">");
-		if(StringHelper.containsNonWhitespace(comment)) {
-			sb.append(comment);
-		}
-		sb.append("</textarea></fieldset>");
-	}
+
 	
 	@Override
 	protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, AssessmentItem assessmentItem,
@@ -580,7 +538,7 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe
 	}
 	
 	private void renderNavigation(AssessmentRenderer renderer, StringOutput sb, AssessmentTestComponent component, URLBuilder ubu, Translator translator) {
-		sb.append("<div class='qtiworks assessmentTest testPartNavigation'>");
+		sb.append("<div id='o_qti_menu' class='qtiworks assessmentTest testPartNavigation'>");
 		
 		//title
 		boolean multiPartTest = component.hasMultipleTestParts();
@@ -630,7 +588,7 @@ public class AssessmentTestComponentRenderer extends AssessmentObjectComponentRe
 	
 	private void renderNavigationAssessmentSection(AssessmentRenderer renderer, StringOutput sb, AssessmentTestComponent component, TestPlanNode sectionNode,
 			URLBuilder ubu, Translator translator) {
-		sb.append("<li class='assessmentSection'>")
+		sb.append("<li class='assessmentSection o_qti_menu_item'>")
 		  .append("<header><h2>").append(sectionNode.getSectionPartTitle()).append("</h2>");
 		renderAssessmentSectionRubrickBlock(renderer, sb, component, sectionNode, ubu, translator);
 
diff --git a/src/test/java/org/olat/core/commons/services/help/spi/ConfluenceLinkSPITest.java b/src/test/java/org/olat/core/commons/services/help/spi/ConfluenceLinkSPITest.java
index e68772670e1..59dedc9b864 100644
--- a/src/test/java/org/olat/core/commons/services/help/spi/ConfluenceLinkSPITest.java
+++ b/src/test/java/org/olat/core/commons/services/help/spi/ConfluenceLinkSPITest.java
@@ -20,10 +20,12 @@
 package org.olat.core.commons.services.help.spi;
 
 import java.util.Locale;
+import java.util.concurrent.Callable;
 
 import org.junit.Assert;
 import org.junit.Test;
 import org.olat.core.helpers.SettingsTest;
+import org.olat.test.OlatTestCase;
 
 /**
  * 
@@ -31,7 +33,7 @@ import org.olat.core.helpers.SettingsTest;
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class ConfluenceLinkSPITest {
+public class ConfluenceLinkSPITest extends OlatTestCase {
 	
 	@Test
 	public void getURL_confluence() {
@@ -93,13 +95,13 @@ public class ConfluenceLinkSPITest {
 		Assert.assertNotNull(url2);
 		Assert.assertTrue(url2.endsWith("Data%20Management#DataManagement-qb_import"));
 		// Wait 5secs and try it again, should be translated now
-		try {
-			Thread.sleep(5000);
-		} catch (InterruptedException e) {
-			e.printStackTrace();
-		}
-		url2 = linkSPI.getURL(Locale.GERMAN, "Data Management#qb_import");
-		Assert.assertNotNull(url2);
-		Assert.assertTrue(url2.endsWith("Handhabung%20der%20Daten#HandhabungderDaten-qb_import"));
+		
+		waitForCondition(new Callable<Boolean>(){
+			@Override
+			public Boolean call() throws Exception {
+				String url3 = linkSPI.getURL(Locale.GERMAN, "Data Management#qb_import");
+				return url3 != null && url3.endsWith("Handhabung%20der%20Daten#HandhabungderDaten-qb_import");
+			}
+		}, 5000);
 	}
 }
diff --git a/src/test/java/org/olat/test/QtiWorksTests.java b/src/test/java/org/olat/test/QtiWorksTests.java
new file mode 100644
index 00000000000..6671d6a37bd
--- /dev/null
+++ b/src/test/java/org/olat/test/QtiWorksTests.java
@@ -0,0 +1,119 @@
+/**
+ * <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.test;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * 
+ * A test suite which run the tests of qtiworks (jqtiplus, the math assess glue
+ * a.k.a Maxima, the samples which contains some integration tests)
+ * 
+ * Initial date: 29.09.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+	//qtiworks-jqtiplus
+	uk.ac.ed.ph.jqtiplus.node.content.ContentTest.class,
+	uk.ac.ed.ph.jqtiplus.node.item.MapResponsePointTest.class,
+	uk.ac.ed.ph.jqtiplus.node.item.MapResponseTest.class,
+	uk.ac.ed.ph.jqtiplus.node.item.TemplateTest.class,
+	uk.ac.ed.ph.jqtiplus.node.item.interaction.TextEntryInteractionTest.class,
+	uk.ac.ed.ph.jqtiplus.reading.QtiXmlReaderTest.class,
+	uk.ac.ed.ph.jqtiplus.reading.QtiObjectReaderTest.class,
+	uk.ac.ed.ph.jqtiplus.running.ChoiceItemBadStateTest.class,
+	uk.ac.ed.ph.jqtiplus.running.ChoiceItemRunningTest.class,
+	uk.ac.ed.ph.jqtiplus.running.ItemProcessorControllerTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestBranchRuleNavigationExitTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestBranchRuleNavigationTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestComplexLinearNavigationTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestIncompleteExitTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestLinearSimultaneousTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestNonlinearIndividualTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestNonlinearNavigationTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestNonlinearSimultaneousTest.class,
+	uk.ac.ed.ph.jqtiplus.running.TestProcessorControllerTest.class,
+	uk.ac.ed.ph.jqtiplus.serialization.QtiSerializerTest.class,
+	uk.ac.ed.ph.jqtiplus.types.ComplexReferenceIdentifierAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.types.ComplexReferenceIdentifierRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.types.IdentifierAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.types.IdentifierRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.BaseTypeAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.BaseTypeRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.BooleanValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.BooleanValueRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.BooleanValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.CardinalityAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.CardinalityRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.DirectedPairValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.DirectedPairValueRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.DirectedPairValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.DurationValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.DurationValueRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.DurationValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.FileValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.FloatValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.FloatValueRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.FloatValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.IdentifierValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.IntegerValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.IntegerValueRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.IntegerValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.MultipleValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.NullValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.OrderedValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.PairValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.PairValueRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.PairValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.PointValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.PointValueRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.PointValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.RecordValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.StringValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.StringValueTest.class,
+	uk.ac.ed.ph.jqtiplus.value.UriValueAcceptTest.class,
+	uk.ac.ed.ph.jqtiplus.value.UriValueRefuseTest.class,
+	uk.ac.ed.ph.jqtiplus.value.UriValueTest.class,
+	uk.ac.ed.ph.jqtiplus.xmlutils.CustomUriSchemeTest.class,
+	
+	//qtiworks mathassess glue
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.CircularMaximaUpConversionTests.class,
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.MaximaDataBinderBadParsingTests.class,
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.MaximaDataBinderFromMaximaAndBackTests.class,
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.MaximaDataBinderTests.class,
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.MaximaDataBinderToMaximaAndBackTests.class,
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.QtiMaximaSessionExecuteStringCircularTests.class,
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.QtiMaximaSessionPassVariableTests.class,
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.QtiMaximaSessionQueryVariableTests.class,
+	uk.ac.ed.ph.qtiworks.mathassess.glue.maxima.QtiMaximaSessionTest.class,
+	
+	//qtiworks samples (some integration tests)
+	uk.ac.ed.ph.qtiworks.test.integration.QtiXmlReaderSampleTests.class,
+	uk.ac.ed.ph.qtiworks.test.integration.SerializationSampleTests.class,
+	uk.ac.ed.ph.qtiworks.test.integration.TemplateProcessingSampleTests.class,
+	uk.ac.ed.ph.qtiworks.test.integration.ValidationSampleTests.class
+	
+})
+public class QtiWorksTests {
+	//
+}
-- 
GitLab