[BUGFIX] Let form framework finisher parseOption respect arrays
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Tests / Unit / Domain / Finishers / AbstractFinisherTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Form\Tests\Unit\Domain\Finishers;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Prophecy\Argument;
19 use Prophecy\Prophecy\ObjectProphecy;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
22 use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
23 use TYPO3\CMS\Form\Domain\Finishers\FinisherContext;
24 use TYPO3\CMS\Form\Domain\Finishers\FinisherVariableProvider;
25 use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
26 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
27
28 /**
29 * Test case
30 */
31 class AbstractFinisherTest extends UnitTestCase
32 {
33 /**
34 * @var array A backup of registered singleton instances
35 */
36 protected $singletonInstances = [];
37
38 /**
39 * Set up
40 */
41 public function setUp()
42 {
43 $this->singletonInstances = GeneralUtility::getSingletonInstances();
44 }
45
46 /**
47 * Tear down
48 */
49 public function tearDown(): void
50 {
51 GeneralUtility::resetSingletonInstances($this->singletonInstances);
52 parent::tearDown();
53 }
54
55 /**
56 * @test
57 */
58 public function parseOptionReturnsNullIfOptionNameIsTranslation(): void
59 {
60 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
61 AbstractFinisher::class,
62 [],
63 '',
64 false
65 );
66
67 $this->assertNull($mockAbstractFinisher->_call('parseOption', 'translation'));
68 }
69
70 /**
71 * @test
72 */
73 public function parseOptionReturnsNullIfOptionNameNotExistsWithinOptions(): void
74 {
75 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
76 AbstractFinisher::class,
77 [],
78 '',
79 false
80 );
81
82 $mockAbstractFinisher->_set('options', []);
83
84 $this->assertNull($mockAbstractFinisher->_call('parseOption', 'foo'));
85 }
86
87 /**
88 * @test
89 */
90 public function parseOptionReturnsNullIfOptionNameNotExistsWithinDefaultOptions(): void
91 {
92 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
93 AbstractFinisher::class,
94 [],
95 '',
96 false
97 );
98
99 $mockAbstractFinisher->_set('options', []);
100
101 $this->assertNull($mockAbstractFinisher->_call('parseOption', 'foo'));
102 }
103
104 /**
105 * @test
106 */
107 public function parseOptionReturnsBoolOptionValuesAsBool(): void
108 {
109 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
110 AbstractFinisher::class,
111 [],
112 '',
113 false
114 );
115
116 $mockAbstractFinisher->_set('options', [
117 'foo1' => false,
118 ]);
119
120 $expected = false;
121
122 $this->assertSame($expected, $mockAbstractFinisher->_call('parseOption', 'foo1'));
123 }
124
125 /**
126 * @test
127 */
128 public function parseOptionReturnsDefaultOptionValueIfOptionNameNotExistsWithinOptionsButWithinDefaultOptions(): void
129 {
130 $expected = 'defaultValue';
131
132 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
133 AbstractFinisher::class,
134 [],
135 '',
136 false,
137 false,
138 true,
139 [
140 'translateFinisherOption'
141 ]
142 );
143
144 $mockAbstractFinisher
145 ->expects($this->any())
146 ->method('translateFinisherOption')
147 ->willReturnArgument(0);
148
149 $mockAbstractFinisher->_set('options', []);
150 $mockAbstractFinisher->_set('defaultOptions', [
151 'subject' => $expected
152 ]);
153
154 $finisherContextProphecy = $this->prophesize(FinisherContext::class);
155
156 $formRuntimeProphecy = $this->prophesize(FormRuntime::class);
157 $formRuntimeProphecy->offsetExists(Argument::cetera())->willReturn(true);
158 $formRuntimeProphecy->offsetGet(Argument::cetera())->willReturn(null);
159
160 $finisherContextProphecy->getFormRuntime(Argument::cetera())
161 ->willReturn($formRuntimeProphecy->reveal());
162 $finisherContextProphecy->getFinisherVariableProvider(Argument::cetera())
163 ->willReturn(new FinisherVariableProvider);
164
165 $mockAbstractFinisher->_set('finisherContext', $finisherContextProphecy->reveal());
166
167 $this->assertSame($expected, $mockAbstractFinisher->_call('parseOption', 'subject'));
168 }
169
170 /**
171 * @test
172 */
173 public function parseOptionReturnsDefaultOptionValueIfOptionValueIsAFormElementReferenceAndTheFormElementValueIsEmpty(): void
174 {
175 $elementIdentifier = 'element-identifier-1';
176 $expected = 'defaultValue';
177
178 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
179 AbstractFinisher::class,
180 [],
181 '',
182 false,
183 false,
184 true,
185 [
186 'translateFinisherOption'
187 ]
188 );
189
190 $mockAbstractFinisher
191 ->expects($this->any())
192 ->method('translateFinisherOption')
193 ->willReturnArgument(0);
194
195 $mockAbstractFinisher->_set('options', [
196 'subject' => '{' . $elementIdentifier . '}'
197 ]);
198 $mockAbstractFinisher->_set('defaultOptions', [
199 'subject' => $expected
200 ]);
201
202 $finisherContextProphecy = $this->prophesize(FinisherContext::class);
203
204 $formRuntimeProphecy = $this->createFormRuntimeProphecy([
205 $elementIdentifier => ''
206 ]);
207
208 $finisherContextProphecy->getFormRuntime(Argument::cetera())
209 ->willReturn($formRuntimeProphecy->reveal());
210
211 $mockAbstractFinisher->_set('finisherContext', $finisherContextProphecy->reveal());
212
213 $this->assertSame($expected, $mockAbstractFinisher->_call('parseOption', 'subject'));
214 }
215
216 /**
217 * @test
218 */
219 public function parseOptionResolvesFormElementReferenceFromTranslation(): void
220 {
221 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
222 AbstractFinisher::class,
223 [],
224 '',
225 false,
226 false,
227 true,
228 [
229 'translateFinisherOption'
230 ]
231 );
232
233 $elementIdentifier = 'element-identifier-1';
234 $elementValue = 'element-value-1';
235 $elementReferenceName = '{' . $elementIdentifier . '}';
236
237 $translationValue = 'subject: ' . $elementReferenceName;
238 $expected = 'subject: ' . $elementValue;
239
240 $mockAbstractFinisher
241 ->expects($this->any())
242 ->method('translateFinisherOption')
243 ->willReturn($translationValue);
244
245 $mockAbstractFinisher->_set('options', [
246 'subject' => ''
247 ]);
248
249 $finisherContextProphecy = $this->prophesize(FinisherContext::class);
250
251 $formRuntimeProphecy = $this->createFormRuntimeProphecy([
252 $elementIdentifier => $elementValue
253 ]);
254
255 $finisherContextProphecy->getFormRuntime(Argument::cetera())
256 ->willReturn($formRuntimeProphecy->reveal());
257
258 $mockAbstractFinisher->_set('finisherContext', $finisherContextProphecy->reveal());
259
260 $this->assertSame($expected, $mockAbstractFinisher->_call('parseOption', 'subject'));
261 }
262
263 /**
264 * @test
265 */
266 public function substituteRuntimeReferencesReturnsArrayIfInputIsArray(): void
267 {
268 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
269 AbstractFinisher::class,
270 [],
271 '',
272 false
273 );
274
275 $formRuntimeProphecy = $this->prophesize(FormRuntime::class);
276
277 $input = ['bar', 'foobar', ['x', 'y']];
278 $expected = ['bar', 'foobar', ['x', 'y']];
279
280 $this->assertSame($expected, $mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal()));
281 }
282
283 /**
284 * @test
285 */
286 public function substituteRuntimeReferencesReturnsStringIfInputIsString(): void
287 {
288 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
289 AbstractFinisher::class,
290 [],
291 '',
292 false
293 );
294
295 $formRuntimeProphecy = $this->prophesize(FormRuntime::class);
296
297 $input = 'foobar';
298 $expected = 'foobar';
299
300 $this->assertSame($expected, $mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal()));
301 }
302
303 /**
304 * @test
305 */
306 public function substituteRuntimeReferencesReturnsValueFromFormRuntimeIfInputReferenceAFormElementIdentifierWhoseValueIsAString(): void
307 {
308 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
309 AbstractFinisher::class,
310 [],
311 '',
312 false
313 );
314
315 $elementIdentifier = 'element-identifier-1';
316 $input = '{' . $elementIdentifier . '}';
317 $expected = 'element-value';
318
319 $formRuntimeProphecy = $this->createFormRuntimeProphecy([
320 $elementIdentifier => $expected
321 ]);
322
323 $this->assertSame($expected, $mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal()));
324 }
325
326 /**
327 * @test
328 */
329 public function substituteRuntimeReferencesReturnsValueFromFormRuntimeIfInputReferenceMultipleFormElementIdentifierWhoseValueIsAString(): void
330 {
331 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
332 AbstractFinisher::class,
333 [],
334 '',
335 false
336 );
337
338 $elementIdentifier1 = 'element-identifier-1';
339 $elementValue1 = 'element-value-1';
340 $elementIdentifier2 = 'element-identifier-2';
341 $elementValue2 = 'element-value-2';
342
343 $input = '{' . $elementIdentifier1 . '},{' . $elementIdentifier2 . '}';
344 $expected = $elementValue1 . ',' . $elementValue2;
345
346 $formRuntimeProphecy = $this->createFormRuntimeProphecy([
347 $elementIdentifier1 => $elementValue1,
348 $elementIdentifier2 => $elementValue2
349 ]);
350
351 $this->assertSame($expected, $mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal()));
352 }
353
354 /**
355 * @test
356 */
357 public function substituteRuntimeReferencesReturnsValueFromFormRuntimeIfInputReferenceAFormElementIdentifierWhoseValueIsAnArray(): void
358 {
359 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
360 AbstractFinisher::class,
361 [],
362 '',
363 false
364 );
365
366 $elementIdentifier = 'element-identifier-1';
367 $input = '{' . $elementIdentifier . '}';
368 $expected = ['bar', 'foobar'];
369
370 $formRuntimeProphecy = $this->createFormRuntimeProphecy([
371 $elementIdentifier => $expected
372 ]);
373
374 $this->assertSame($expected, $mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal()));
375 }
376
377 /**
378 * @test
379 */
380 public function substituteRuntimeReferencesReturnsValueFromFormRuntimeIfInputIsArrayAndSomeItemsReferenceAFormElementIdentifierWhoseValueIsAnArray(): void
381 {
382 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
383 AbstractFinisher::class,
384 [],
385 '',
386 false
387 );
388
389 $elementIdentifier1 = 'element-identifier-1';
390 $elementValue1 = ['klaus', 'fritz'];
391 $elementIdentifier2 = 'element-identifier-2';
392 $elementValue2 = ['stan', 'steve'];
393
394 $input = [
395 '{' . $elementIdentifier1 . '}',
396 'static value',
397 'norbert' => [
398 'lisa',
399 '{' . $elementIdentifier1 . '}',
400 '{' . $elementIdentifier2 . '}',
401 ],
402 ];
403 $expected = [
404 ['klaus', 'fritz'],
405 'static value',
406 'norbert' => [
407 'lisa',
408 ['klaus', 'fritz'],
409 ['stan', 'steve'],
410 ]
411 ];
412
413 $formRuntimeProphecy = $this->createFormRuntimeProphecy([
414 $elementIdentifier1 => $elementValue1,
415 $elementIdentifier2 => $elementValue2
416 ]);
417
418 $this->assertSame($expected, $mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal()));
419 }
420
421 /**
422 * @test
423 */
424 public function substituteRuntimeReferencesReturnsNoReplacedValueIfInputReferenceANonExistingFormElement(): void
425 {
426 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
427 AbstractFinisher::class,
428 [],
429 '',
430 false
431 );
432
433 $elementIdentifier = 'element-identifier-1';
434 $input = '{' . $elementIdentifier . '}';
435 $expected = '{' . $elementIdentifier . '}';
436
437 $formRuntimeProphecy = $this->createFormRuntimeProphecy([
438 $elementIdentifier => $expected
439 ]);
440
441 $finisherContextProphecy = $this->prophesize(FinisherContext::class);
442 $finisherContextProphecy->getFinisherVariableProvider(Argument::cetera())->willReturn(new FinisherVariableProvider);
443 $mockAbstractFinisher->_set('finisherContext', $finisherContextProphecy->reveal());
444
445 $this->assertSame($expected, $mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal()));
446 }
447
448 /**
449 * @test
450 */
451 public function substituteRuntimeReferencesReturnsTimestampIfInputIsATimestampRequestTrigger(): void
452 {
453 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
454 AbstractFinisher::class,
455 [],
456 '',
457 false
458 );
459
460 $input = '{__currentTimestamp}';
461 $expected = '#^([0-9]{10})$#';
462
463 $formRuntimeProphecy = $this->prophesize(FormRuntime::class);
464
465 $this->assertEquals(1, preg_match($expected, (string)$mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal())));
466 }
467
468 /**
469 * @test
470 */
471 public function substituteRuntimeReferencesThrowsExceptionOnMultipleVariablesResolvedAsArray(): void
472 {
473 $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
474 AbstractFinisher::class,
475 [],
476 '',
477 false
478 );
479
480 $elementIdentifier = 'element-identifier-1';
481 $input = 'BEFORE {' . $elementIdentifier . '} AFTER';
482
483 $formRuntimeProphecy = $this->createFormRuntimeProphecy([
484 $elementIdentifier => ['value-1', 'value-2']
485 ]);
486
487 $finisherContextProphecy = $this->prophesize(FinisherContext::class);
488 $finisherContextProphecy->getFinisherVariableProvider(Argument::cetera())->willReturn(new FinisherVariableProvider);
489 $mockAbstractFinisher->_set('finisherContext', $finisherContextProphecy->reveal());
490
491 $this->expectException(FinisherException::class);
492 $this->expectExceptionCode(1519239265);
493
494 $mockAbstractFinisher->_call(
495 'substituteRuntimeReferences',
496 $input,
497 $formRuntimeProphecy->reveal()
498 );
499 }
500
501 /**
502 * @param array $values Key/Value pairs to be retrievable
503 * @return ObjectProphecy|FormRuntime
504 */
505 protected function createFormRuntimeProphecy(array $values)
506 {
507 /** @var ObjectProphecy|FormRuntime $formRuntimeProphecy */
508 $formRuntimeProphecy = $this->prophesize(FormRuntime::class);
509 foreach ($values as $key => $value) {
510 $formRuntimeProphecy->offsetExists(Argument::exact($key))->willReturn(true);
511 $formRuntimeProphecy->offsetGet(Argument::exact($key))->willReturn($value);
512 }
513 return $formRuntimeProphecy;
514 }
515 }