1<?php 2 3namespace Drupal\Tests\Core\Form; 4 5use Drupal\Component\Utility\NestedArray; 6use Drupal\Core\Form\FormState; 7use Drupal\Core\Form\FormStateInterface; 8use Drupal\Core\Form\SubformState; 9use Drupal\Core\Form\SubformStateInterface; 10use Drupal\Tests\UnitTestCase; 11 12/** 13 * @coversDefaultClass \Drupal\Core\Form\SubformState 14 * 15 * @group Form 16 */ 17class SubformStateTest extends UnitTestCase { 18 19 /** 20 * The form state's values test fixture. 21 * 22 * @var mixed[] 23 */ 24 protected $formStateValues = [ 25 'foo' => 'bar', 26 'dog' => [ 27 'breed' => 'Pit bull', 28 'name' => 'Dodger', 29 ], 30 ]; 31 32 /** 33 * The parent form. 34 * 35 * @var mixed[] 36 */ 37 protected $parentForm = [ 38 '#parents' => [], 39 'foo' => [ 40 '#parents' => ['foo'], 41 '#array_parents' => ['foo'], 42 ], 43 'dog' => [ 44 '#parents' => ['dog'], 45 '#array_parents' => ['dog'], 46 'breed' => [ 47 '#parents' => ['dog', 'breed'], 48 '#array_parents' => ['dog', 'breed'], 49 ], 50 'name' => [ 51 '#parents' => ['dog', 'name'], 52 '#array_parents' => ['dog', 'name'], 53 ], 54 ], 55 ]; 56 57 /** 58 * @covers ::getValues 59 * @covers ::getParents 60 * 61 * @dataProvider providerGetValues 62 * 63 * @param string[] $parents 64 * @param string $expected 65 */ 66 public function testGetValues(array $parents, $expected) { 67 $parent_form_state = new FormState(); 68 $parent_form_state->setValues($this->formStateValues); 69 70 $subform = NestedArray::getValue($this->parentForm, $parents); 71 $subform_state = SubformState::createForSubform($subform, $this->parentForm, $parent_form_state); 72 $subform_state_values = &$subform_state->getValues(); 73 $this->assertSame($expected, $subform_state_values); 74 75 // Modify the retrieved values and confirm they are modified by reference in 76 // the parent form state. 77 $subform_state_values['fish'] = 'Jim'; 78 $this->assertSame($subform_state_values, $subform_state->getValues()); 79 } 80 81 /** 82 * Provides data to self::testGetValues(). 83 */ 84 public function providerGetValues() { 85 $data = []; 86 $data['exist'] = [ 87 ['dog'], 88 $this->formStateValues['dog'], 89 ]; 90 91 return $data; 92 } 93 94 /** 95 * @covers ::getValues 96 * @covers ::getParents 97 * 98 * @dataProvider providerGetValuesBroken 99 * 100 * @param string[] $parents 101 * @param string $expected 102 */ 103 public function testGetValuesBroken(array $parents, $expected) { 104 $this->expectException(\UnexpectedValueException::class); 105 $this->testGetValues($parents, $expected); 106 } 107 108 /** 109 * Provides data to self::testGetValuesBroken(). 110 */ 111 public function providerGetValuesBroken() { 112 $data = []; 113 $data['exist'] = [ 114 ['foo'], 115 $this->formStateValues['foo'], 116 ]; 117 $data['nested'] = [ 118 ['dog', 'name'], 119 'Dodger', 120 ]; 121 122 return $data; 123 } 124 125 /** 126 * @covers ::getValue 127 * 128 * @dataProvider providerTestGetValue 129 */ 130 public function testGetValue($parents, $key, $expected, $default = NULL) { 131 $parent_form_state = new FormState(); 132 $parent_form_state->setValues($this->formStateValues); 133 134 $subform = NestedArray::getValue($this->parentForm, $parents); 135 $subform_state = SubformState::createForSubform($subform, $this->parentForm, $parent_form_state); 136 $subform_state_value = &$subform_state->getValue($key, $default); 137 $this->assertSame($expected, $subform_state_value); 138 139 // Modify the retrieved values and confirm they are modified by reference in 140 // the parent form state. 141 $subform_state_value = 'Jim'; 142 $this->assertSame($subform_state_value, $subform_state->getValue($key)); 143 } 144 145 /** 146 * Provides data to self::testGetValue(). 147 */ 148 public function providerTestGetValue() { 149 $data = []; 150 $data['exist'] = [ 151 ['dog'], 152 'name', 153 'Dodger', 154 ]; 155 156 return $data; 157 } 158 159 /** 160 * @covers ::getValue 161 * 162 * @dataProvider providerTestGetValueBroken 163 */ 164 public function testGetValueBroken(array $parents, $key, $expected, $default = NULL) { 165 $this->expectException(\UnexpectedValueException::class); 166 $this->testGetValue($parents, $key, $expected, $default); 167 } 168 169 /** 170 * Provides data to self::testGetValueBroken(). 171 */ 172 public function providerTestGetValueBroken() { 173 $data = []; 174 $data['nested'] = [ 175 ['dog', 'name'], 176 NULL, 177 'Dodger', 178 ]; 179 180 return $data; 181 } 182 183 /** 184 * @covers ::setValues 185 * 186 * @dataProvider providerTestSetValues 187 */ 188 public function testSetValues($parents, $new_values, $expected) { 189 $parent_form_state = new FormState(); 190 $parent_form_state->setValues($this->formStateValues); 191 192 $subform = NestedArray::getValue($this->parentForm, $parents); 193 $subform_state = SubformState::createForSubform($subform, $this->parentForm, $parent_form_state); 194 $this->assertSame($subform_state, $subform_state->setValues($new_values)); 195 $this->assertSame($expected, $parent_form_state->getValues()); 196 } 197 198 /** 199 * Provides data to self::testSetValues(). 200 */ 201 public function providerTestSetValues() { 202 $data = []; 203 $data['exist'] = [ 204 ['dog'], 205 [], 206 [ 207 'foo' => 'bar', 208 'dog' => [], 209 ], 210 ]; 211 return $data; 212 } 213 214 /** 215 * @covers ::setValues 216 * 217 * @dataProvider providerTestSetValuesBroken 218 */ 219 public function testSetValuesBroken($parents, $new_values, $expected) { 220 $this->expectException(\UnexpectedValueException::class); 221 $this->testSetValues($parents, $new_values, $expected); 222 } 223 224 /** 225 * Provides data to self::testSetValuesBroken(). 226 */ 227 public function providerTestSetValuesBroken() { 228 $data = []; 229 $data['exist'] = [ 230 ['foo'], 231 [], 232 [ 233 'foo' => [], 234 'dog' => $this->formStateValues['dog'], 235 ], 236 ]; 237 return $data; 238 } 239 240 /** 241 * @covers ::getCompleteFormState 242 */ 243 public function testGetCompleteFormStateWithParentCompleteForm() { 244 $parent_form_state = $this->prophesize(FormStateInterface::class); 245 $subform_state = SubformState::createForSubform($this->parentForm['dog'], $this->parentForm, $parent_form_state->reveal()); 246 $this->assertSame($parent_form_state->reveal(), $subform_state->getCompleteFormState()); 247 } 248 249 /** 250 * @covers ::getCompleteFormState 251 */ 252 public function testGetCompleteFormStateWithParentSubform() { 253 $complete_form_state = $this->prophesize(FormStateInterface::class); 254 $parent_form_state = $this->prophesize(SubformStateInterface::class); 255 $parent_form_state->getCompleteFormState() 256 ->willReturn($complete_form_state->reveal()) 257 ->shouldBeCalled(); 258 $subform_state = SubformState::createForSubform($this->parentForm['dog'], $this->parentForm, $parent_form_state->reveal()); 259 $this->assertSame($complete_form_state->reveal(), $subform_state->getCompleteFormState()); 260 } 261 262 /** 263 * @covers ::setLimitValidationErrors 264 */ 265 public function testSetLimitValidationErrors() { 266 $parent_limit_validation_errors = ['dog', 'name']; 267 $limit_validation_errors = ['name']; 268 269 $parent_form_state = $this->prophesize(FormStateInterface::class); 270 $parent_form_state->setLimitValidationErrors($parent_limit_validation_errors) 271 ->shouldBeCalled(); 272 273 $subform_state = SubformState::createForSubform($this->parentForm['dog'], $this->parentForm, $parent_form_state->reveal()); 274 $this->assertSame($subform_state, $subform_state->setLimitValidationErrors($limit_validation_errors)); 275 } 276 277 /** 278 * @covers ::getLimitValidationErrors 279 */ 280 public function testGetLimitValidationErrors() { 281 $parent_limit_validation_errors = ['dog', 'name']; 282 $limit_validation_errors = ['name']; 283 284 $parent_form_state = $this->prophesize(FormStateInterface::class); 285 $parent_form_state->getLimitValidationErrors() 286 ->willReturn($parent_limit_validation_errors) 287 ->shouldBeCalled(); 288 289 $subform_state = SubformState::createForSubform($this->parentForm['dog'], $this->parentForm, $parent_form_state->reveal()); 290 $this->assertSame($limit_validation_errors, $subform_state->getLimitValidationErrors()); 291 } 292 293 /** 294 * @covers ::setErrorByName 295 */ 296 public function testSetErrorByName() { 297 $parent_form_error_name = 'dog][name'; 298 $subform_error_name = 'name'; 299 // cSpell:disable-next-line 300 $message = 'De kat krabt de krullen van de trap.'; 301 302 $parent_form_state = $this->prophesize(FormStateInterface::class); 303 $parent_form_state->setErrorByName($parent_form_error_name, $message) 304 ->shouldBeCalled(); 305 306 $subform_state = SubformState::createForSubform($this->parentForm['dog'], $this->parentForm, $parent_form_state->reveal()); 307 $this->assertSame($subform_state, $subform_state->setErrorByName($subform_error_name, $message)); 308 } 309 310} 311