public function testCartesianResponse()
 {
     $res = ArrayUtil::cartesianProductForResponses([[new ValidResponse(1, ['a', 'b']), new ValidResponse(2, ['c', 'd'])], [new ValidResponse(3, ['a', 'e'])]]);
     $this->assertCount(2, $res);
     $this->assertEquals(['a', 'b', 'a', 'e'], $res[0]->getValue());
     $this->assertEquals(4, $res[0]->getScore());
     $this->assertEquals(['c', 'd', 'a', 'e'], $res[1]->getValue());
     $this->assertEquals(5, $res[1]->getScore());
 }
 public function testMergedTextInteraction()
 {
     $file = $this->getFixtureFileContents('interactions/textentryinteraction.xml');
     list($item, $questions) = $this->mapper->parse($file);
     /** @var \LearnosityQti\Entities\Item\item $item */
     $this->assertInstanceOf('LearnosityQti\\Entities\\Item\\item', $item);
     $this->assertTrue($item->get_reference() === 'res_AA-FIB_B13_CH1_geoc_f1f1');
     $this->assertTrue($item->get_status() === 'published');
     $this->assertTrue(count($item->get_questionReferences()) === 1);
     $this->assertTrue(substr_count($item->get_content(), '<span class="learnosity-response question-' . $item->get_questionReferences()[0] . '"></span>') === 1);
     $this->assertTrue(count($questions) === 1);
     $q = $questions[0];
     $this->assertInstanceOf('\\LearnosityQti\\Entities\\Question', $q);
     /* @var $q \LearnosityQti\Entities\Question */
     $this->assertTrue($q->get_type() === 'clozetext');
     /* @var $questionType \LearnosityQti\Entities\QuestionTypes\clozetext */
     $questionType = $q->get_data();
     $this->assertInstanceOf('\\LearnosityQti\\Entities\\QuestionTypes\\clozetext', $questionType);
     $this->assertEmpty($questionType->get_stimulus());
     $this->assertTrue($questionType->get_type() === 'clozetext');
     $this->assertTrue(substr_count($questionType->get_template(), '{{response}}') === 2);
     /* @var $validation \LearnosityQti\Entities\QuestionTypes\clozetext_validation */
     $validation = $questionType->get_validation();
     $this->assertInstanceOf('\\LearnosityQti\\Entities\\QuestionTypes\\clozetext_validation', $validation);
     $this->assertEquals($validation->get_scoring_type(), 'exactMatch');
     /* @var $validResponse \LearnosityQti\Entities\QuestionTypes\clozetext_validation_valid_response */
     $validResponse = $validation->get_valid_response();
     $this->assertInstanceOf('\\LearnosityQti\\Entities\\QuestionTypes\\clozetext_validation_valid_response', $validResponse);
     $this->assertEquals(6, $validResponse->get_score());
     $options = [];
     $options[] = $validResponse->get_value();
     $altResponses = $validation->get_alt_responses();
     $this->assertTrue(count($altResponses) === 3);
     /* @var $altResponse \LearnosityQti\Entities\QuestionTypes\clozetext_validation_alt_responses_item */
     foreach ($altResponses as $altResponse) {
         $this->assertInstanceOf('\\LearnosityQti\\Entities\\QuestionTypes\\clozetext_validation_alt_responses_item', $altResponse);
         $options[] = $altResponse->get_value();
     }
     $this->assertEquals(5, $altResponses[0]->get_score());
     $this->assertEquals(5, $altResponses[1]->get_score());
     $this->assertEquals(4, $altResponses[2]->get_score());
     $expectedOptions = ArrayUtil::cartesianProduct([['a', 'b'], ['OHMYGOD', 'x7']]);
     $matchCount = 0;
     foreach ($expectedOptions as $expectedKey => $expectedValue) {
         foreach ($options as $optionKey => $optionValue) {
             $diff = array_diff($expectedValue, $optionValue);
             if (!$diff) {
                 $matchCount++;
             }
         }
     }
     $this->assertEquals($matchCount, count($options));
 }
 protected function getMapResponseTemplateValidation()
 {
     $mapEntryValueMap = [];
     foreach ($this->responseDeclaration->getMapping()->getMapEntries() as $mapEntry) {
         /** @var MapEntry $mapEntry */
         $mapEntryValueMap[] = ['key' => $this->hottextComponents[$mapEntry->getMapKey()], 'score' => $mapEntry->getMappedValue()];
     }
     $combinations = ArrayUtil::combinations($mapEntryValueMap);
     $correctResponses = [];
     foreach ($combinations as $combination) {
         if (count($combination) > 0 && count($combination) <= $this->maxChoices) {
             $score = array_sum(array_column($combination, 'score'));
             $value = array_column($combination, 'key');
             $correctResponses[] = new ValidResponse($score, $value);
         }
     }
     return ValidationBuilder::build('tokenhighlight', 'exactMatch', $correctResponses);
 }
 protected function getMapResponseTemplateValidation()
 {
     $interactionResponses = [];
     /** @var ResponseDeclaration $responseDeclaration */
     foreach ($this->responseDeclarations as $responseIdentifier => $responseDeclaration) {
         $responses = [];
         foreach ($responseDeclaration->getMapping()->getMapEntries()->getArrayCopy(true) as $mapEntry) {
             /** @var MapEntry $mapEntry */
             $responses[] = new ValidResponse($mapEntry->getMappedValue(), [$this->possibleResponses[$responseIdentifier][$mapEntry->getMapKey()]]);
             // Find out if one of them is case sensitive
             if (!$mapEntry->isCaseSensitive()) {
                 LogService::log('Could not support `caseSensitive` attribute for this interaction type. This question validation is always case sensitive');
             }
         }
         $interactionResponses[] = $responses;
     }
     $responses = ArrayUtil::cartesianProductForResponses($interactionResponses);
     return ValidationBuilder::build('clozedropdown', 'exactMatch', $responses);
 }
 protected function getMapResponseTemplateValidation()
 {
     $interactionResponses = [];
     /** @var ResponseDeclaration $responseDeclaration */
     foreach ($this->responseDeclarations as $responseIdentifier => $responseDeclaration) {
         $responses = [];
         foreach ($responseDeclaration->getMapping()->getMapEntries()->getArrayCopy(true) as $mapEntry) {
             /** @var MapEntry $mapEntry */
             $responses[] = new ValidResponse($mapEntry->getMappedValue(), [$mapEntry->getMapKey()]);
             // Find out if one of them is case sensitive
             if ($mapEntry->isCaseSensitive()) {
                 $this->isCaseSensitive = true;
             }
         }
         $interactionResponses[] = $responses;
     }
     $responses = ArrayUtil::cartesianProductForResponses($interactionResponses);
     return ValidationBuilder::build('clozetext', 'exactMatch', $responses);
 }
 protected function getMapResponseTemplateValidation()
 {
     $validResponses = [];
     foreach ($this->responseDeclaration->getMapping()->getMapEntries() as $mapEntry) {
         /** @var MapEntry $mapEntry */
         if (!isset($this->options[$mapEntry->getMapKey()])) {
             LogService::log('Invalid choice `' . $mapEntry->getMapKey() . '`');
             continue;
         }
         if ($mapEntry->getMappedValue() < 0) {
             LogService::log('Invalid score ` ' . $mapEntry->getMappedValue() . ' `. Negative score is ignored');
             continue;
         }
         $validResponses[] = new ValidResponse($mapEntry->getMappedValue(), [$mapEntry->getMapKey()]);
     }
     // Handle `multiple` cardinality
     if ($this->responseDeclaration->getCardinality() === Cardinality::MULTIPLE) {
         $combinationChoicesCount = $this->maxChoices === 0 ? count($validResponses) : $this->maxChoices;
         $combinationResponses = ArrayUtil::combinations($validResponses, $combinationChoicesCount);
         $validResponses = ArrayUtil::combineValidResponsesWithSummedScore($combinationResponses);
     }
     return ValidationBuilder::build('mcq', 'exactMatch', $validResponses);
 }
 protected function getMapResponseTemplateValidation()
 {
     $gapIdentifiersIndexMap = array_flip($this->gapIdentifiers);
     $responseIndexSet = [];
     $responses = [];
     foreach ($this->responseDeclaration->getMapping()->getMapEntries() as $mapEntry) {
         /** @var MapEntry $mapEntry */
         /** @var QtiDirectedPair $mapKey */
         $mapKey = $mapEntry->getMapKey();
         // Map response value and index based from the `mapKey`, try to guess which one is which since they
         // apparently can swap :(
         if (isset($this->possibleResponses[$mapKey->getFirst()]) && isset($gapIdentifiersIndexMap[$mapKey->getSecond()])) {
             $responseValue = $this->possibleResponses[$mapKey->getFirst()];
             $responseIndex = $gapIdentifiersIndexMap[$mapKey->getSecond()];
         } else {
             if (isset($this->possibleResponses[$mapKey->getSecond()]) && isset($gapIdentifiersIndexMap[$mapKey->getFirst()])) {
                 $responseValue = $this->possibleResponses[$mapKey->getSecond()];
                 $responseIndex = $gapIdentifiersIndexMap[$mapKey->getFirst()];
             } else {
                 throw new MappingException('Fail to match identifiers on `mapKey` attribute from `mapping`');
             }
         }
         // Check for duplicated response
         if (!$this->isDuplicatedResponse) {
             if (!isset($responseIndexSet[$responseValue])) {
                 $responseIndexSet[$responseValue] = true;
             } else {
                 $this->isDuplicatedResponse = true;
             }
         }
         // Wrap the identifier => score into an array as the identifier can be duplicated (Duplicated Response)
         // Build ValidResponse object array in the correct order matching the `gap` elements
         $responses[$responseIndex][] = new ValidResponse($mapEntry->getMappedValue(), [$responseValue]);
     }
     $this->assertEachGapHasCorrespondingValidResponses($responses);
     $responses = ArrayUtil::cartesianProductForResponses($responses);
     return ValidationBuilder\ValidationBuilder::build($this->questionTypeName, 'exactMatch', $responses);
 }