diff --git a/question.php b/question.php index 1f780365..92864398 100644 --- a/question.php +++ b/question.php @@ -959,9 +959,18 @@ public function has_combined_unit_field(): bool { // Furthermore, there must be either a {_0}{_u} without whitespace in the part's text // (meaning the user explicitly wants a combined unit field) or no answer box placeholders - // at all, neither for the answer nor for the unit. - $combinedrequested = strpos($this->subqtext, '{_0}{_u}') !== false; - $noplaceholders = strpos($this->subqtext, '{_0}') === false && strpos($this->subqtext, '{_u}') === false; + // at all, neither for the answer nor for the unit. As the placeholders may contain formatting + // options, it makes sense to first simplify the part's question text by removing those. Note + // that the regex is different from e. g. the one in scan_for_answer_boxes(), because there + // MUST NOT be an :options-variable or a :MC/:MCE/:MCES/:MCS part. + $simplifiedtext = preg_replace( + '/\{(_u|_\d+)((\|[\w =#]*)*)\}/', + '{\1}', + $this->subqtext, + ); + + $combinedrequested = strpos($simplifiedtext, '{_0}{_u}') !== false; + $noplaceholders = strpos($simplifiedtext, '{_0}') === false && strpos($this->subqtext, '{_u}') === false; return $combinedrequested || $noplaceholders; } diff --git a/renderer.php b/renderer.php index 78c0f823..9c3d7365 100644 --- a/renderer.php +++ b/renderer.php @@ -666,10 +666,16 @@ public function get_part_formulation(question_attempt $qa, question_display_opti // If part has combined unit answer input. if ($part->has_combined_unit_field()) { + // For a combined unit field, we try to merge the formatting options from the {_0} and the + // {_u} placeholder, giving precedence to the latter. + $mergedformat = $boxes['_u']['format'] + $boxes['_0']['format']; $combinedfieldhtml = $this->create_input_box( - $part, self::COMBINED_FIELD, $qa, $options, $boxes[$placeholder]['format'], $sub->feedbackclass + $part, self::COMBINED_FIELD, $qa, $options, $mergedformat, $sub->feedbackclass ); - return str_replace('{_0}{_u}', $combinedfieldhtml, $subqreplaced); + // The combined field must be placed where the user has the {_0}{_u} placeholders, possibly with + // their formatting options. + $boxplaceholders = $boxes['_0']['placeholder'] . $boxes['_u']['placeholder']; + return str_replace($boxplaceholders, $combinedfieldhtml, $subqreplaced); } // Iterate over all boxes again, this time creating the appropriate input control and insert it diff --git a/tests/renderer_test.php b/tests/renderer_test.php index 722ff9b6..6f134125 100644 --- a/tests/renderer_test.php +++ b/tests/renderer_test.php @@ -394,6 +394,85 @@ public function test_render_formatted_input_box($styles, $placeholder): void { $this->check_current_output(...$expectations); } + /** + * Data provider. + * + * @return array + */ + public static function provide_combined_box_formatting(): array { + return [ + [[], '{_0}{_u}'], + [['width: 100px'], '{_0|w=100px}{_u}'], + [['width: 100px'], '{_0}{_u|w=100px}'], + [['width: 80px', 'background-color: blue'], '{_0|w=100px|bgcol=red}{_u|w=80px|bgcol=blue}'], + [['width: 100px', 'background-color: red'], '{_0|w=100px|bgcol=red}{_u}'], + [['width: 100px', 'background-color: red'], '{_0}{_u|w=100px|bgcol=red}'], + [['width: 80px', 'background-color: blue'], '{_0|w=100px|bgcol=red}{_u|w=80px|bgcol=blue}'], + [['width: 80px', 'background-color: blue', 'text-align: right'], '{_0|w=100px|align=right}{_u|w=80px|bgcol=blue}'], + ]; + } + + /** + * Test formatting of combined unit field works as expected. + * + * @param array $styles (combined) style settings to be checked for + * @param string $placeholder placeholder definition with formatting + * @return void + * + * @dataProvider provide_combined_box_formatting + */ + public function test_formatting_of_combined_unit_box($styles, $placeholder): void { + $q = $this->get_test_formulas_question('testsinglenumunit'); + $q->parts[0]->subqtext = $placeholder; + $this->start_attempt_at_question($q, 'immediatefeedback', 1); + + // Check that there is a combined unit field and no other fields or stray placeholders. + $this->render(); + $this->check_output_contains_text_input('0_'); + $this->check_output_does_not_contain_text_input_with_class('0_0'); + $this->check_output_does_not_contain_text_input_with_class('0_1'); + $this->check_output_does_not_contain_stray_placeholders(); + + // Check the formatting. + $expectations = []; + foreach ($styles as $style) { + $expectations[] = $this->get_contains_input_with_css_expectation($style); + } + $this->check_current_output(...$expectations); + } + + /** + * Test formatting of separate unit field works as expected. + * + * @param array $styles style settings to be checked for + * @param string $placeholder placeholder definition with formatting + * @return void + * + * @dataProvider provide_styles + */ + public function test_formatting_of_separate_unit_box($styles, $placeholder): void { + // We take the formatting intended for the number box and use it for the unit box. Also, + // we add a placeholder for an unformatted number box in front of it. + $placeholder = str_replace('{_0', '{_0} {_u', $placeholder); + $q = $this->get_test_formulas_question('testsinglenumunit'); + $q->parts[0]->subqtext = $placeholder; + $this->start_attempt_at_question($q, 'immediatefeedback', 1); + + // There must be a number box and a unit box, no combined field and no stray placeholders. + $this->render(); + $this->check_output_contains_text_input('0_0'); + $this->check_output_contains_text_input('0_1'); + $this->check_output_does_not_contain_text_input_with_class('0_'); + $this->check_output_does_not_contain_stray_placeholders(); + + // Check the formatting. + $expectations = []; + foreach ($styles as $style) { + $expectations[] = $this->get_contains_input_with_css_expectation($style); + } + $this->check_current_output(...$expectations); + } + /** * Data provider. *