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