diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 0a5cd42cfd..ff8faad141 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -255,6 +255,17 @@ {{ block('form_widget_simple') }} {%- endblock color_widget -%} +{%- block week_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
+ {{ form_widget(form.year, vars) }}-{{ form_widget(form.week, vars) }} +
+ {%- endif -%} +{%- endblock week_widget -%} + {# Labels #} {%- block form_label -%} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php index d7868b274b..6fb468b9dd 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -2722,6 +2722,104 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest [@name="name"] [@class="my&class form-control"] [@value="#0000ff"] +' + ); + } + + public function testWeekSingleText() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ + 'input' => 'string', + 'widget' => 'single_text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/input + [@type="week"] + [@name="holidays"] + [@class="my&class form-control"] + [@value="1970-W01"] + [not(@maxlength)] +' + ); + } + + public function testWeekSingleTextNoHtml5() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ + 'input' => 'string', + 'widget' => 'single_text', + 'html5' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/input + [@type="text"] + [@name="holidays"] + [@class="my&class form-control"] + [@value="1970-W01"] + [not(@maxlength)] +' + ); + } + + public function testWeekChoices() + { + $this->requiresFeatureSet(404); + + $data = ['year' => date('Y'), 'week' => 1]; + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ + 'input' => 'array', + 'required' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/div + [@class="my&class"] + [ + ./select + [@id="name_year"] + [@class="form-control"] + [./option[@value="'.$data['year'].'"][@selected="selected"]] + /following-sibling::select + [@id="name_week"] + [@class="form-control"] + [./option[@value="'.$data['week'].'"][@selected="selected"]] + ] + [count(.//select)=2]' + ); + } + + public function testWeekText() + { + $this->requiresFeatureSet(404); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ + 'input' => 'string', + 'widget' => 'text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/div + [@class="my&class"] + [ + ./input + [@id="name_year"] + [@type="number"] + [@class="form-control"] + [@value="2000"] + /following-sibling::input + [@id="name_week"] + [@type="number"] + [@class="form-control"] + [@value="1"] + ] + [count(./input)=2] ' ); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/week_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/week_widget.html.php new file mode 100644 index 0000000000..610b6e0c19 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/week_widget.html.php @@ -0,0 +1,14 @@ + + block($form, 'form_widget_simple'); ?> + + ['size' => 1]] : [] ?> +
block($form, 'widget_container_attributes') ?>> + widget($form['year'], $vars); + echo '-'; + echo $view['form']->widget($form['week'], $vars); + ?> +
+ diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index ba3fb1d226..99e7078f0f 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.4.0 ----- + * add new `WeekType` * using different values for the "model_timezone" and "view_timezone" options of the `TimeType` without configuring a reference date is deprecated * preferred choices are repeated in the list of all choices diff --git a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php index b658b9979f..1f3431cb49 100644 --- a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php +++ b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php @@ -83,6 +83,7 @@ class CoreExtension extends AbstractExtension new Type\CurrencyType(), new Type\TelType(), new Type\ColorType(), + new Type\WeekType(), ]; } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/WeekToArrayTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/WeekToArrayTransformer.php new file mode 100644 index 0000000000..51475e235c --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/WeekToArrayTransformer.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between an ISO 8601 week date string and an array. + * + * @author Damien Fayet + */ +class WeekToArrayTransformer implements DataTransformerInterface +{ + /** + * Transforms a string containing an ISO 8601 week date into an array. + * + * @param string|null $value A week date string + * + * @return array A value containing year and week + * + * @throws TransformationFailedException If the given value is not a string, + * or if the given value does not follow the right format + */ + public function transform($value) + { + if (null === $value) { + return ['year' => null, 'week' => null]; + } + + if (!\is_string($value)) { + throw new TransformationFailedException(sprintf('Value is expected to be a string but was "%s".', \is_object($value) ? \get_class($value) : \gettype($value))); + } + + if (0 === preg_match('/^(?P\d{4})-W(?P\d{2})$/', $value, $matches)) { + throw new TransformationFailedException('Given data does not follow the date format "Y-\WW".'); + } + + return [ + 'year' => (int) $matches['year'], + 'week' => (int) $matches['week'], + ]; + } + + /** + * Transforms an array into a week date string. + * + * @param array $value An array containing a year and a week number + * + * @return string|null A week date string following the format Y-\WW + * + * @throws TransformationFailedException If the given value can not be merged in a valid week date string, + * or if the obtained week date does not exists + */ + public function reverseTransform($value) + { + if (null === $value || [] === $value) { + return null; + } + + if (!\is_array($value)) { + throw new TransformationFailedException(sprintf('Value is expected to be an array, but was "%s".', \is_object($value) ? \get_class($value) : \gettype($value))); + } + + if (!\array_key_exists('year', $value)) { + throw new TransformationFailedException('Key "year" is missing.'); + } + + if (!\array_key_exists('week', $value)) { + throw new TransformationFailedException('Key "week" is missing.'); + } + + if ($additionalKeys = array_diff(array_keys($value), ['year', 'week'])) { + throw new TransformationFailedException(sprintf('Expected only keys "year" and "week" to be present, but also got ["%s"].', implode('", "', $additionalKeys))); + } + + if (null === $value['year'] && null === $value['week']) { + return null; + } + + if (!\is_int($value['year'])) { + throw new TransformationFailedException(sprintf('Year is expected to be an integer, but was "%s".', \is_object($value['year']) ? \get_class($value['year']) : \gettype($value['year']))); + } + + if (!\is_int($value['week'])) { + throw new TransformationFailedException(sprintf('Week is expected to be an integer, but was "%s".', \is_object($value['week']) ? \get_class($value['week']) : \gettype($value['week']))); + } + + // The 28th December is always in the last week of the year + if (date('W', strtotime('28th December '.$value['year'])) < $value['week']) { + throw new TransformationFailedException(sprintf('Week "%d" does not exist for year "%d".', $value['week'], $value['year'])); + } + + return sprintf('%d-W%02d', $value['year'], $value['week']); + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php b/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php new file mode 100644 index 0000000000..70e0000ad9 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/Type/WeekType.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\DataTransformer\WeekToArrayTransformer; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\ReversedTransformer; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class WeekType extends AbstractType +{ + private static $widgets = [ + 'text' => IntegerType::class, + 'choice' => ChoiceType::class, + ]; + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + if ('string' === $options['input']) { + $builder->addModelTransformer(new WeekToArrayTransformer()); + } + + if ('single_text' === $options['widget']) { + $builder->addViewTransformer(new ReversedTransformer(new WeekToArrayTransformer())); + } else { + $yearOptions = $weekOptions = [ + 'error_bubbling' => true, + 'empty_data' => '', + ]; + // when the form is compound the entries of the array are ignored in favor of children data + // so we need to handle the cascade setting here + $emptyData = $builder->getEmptyData() ?: []; + + $yearOptions['empty_data'] = $emptyData['year'] ?? ''; + $weekOptions['empty_data'] = $emptyData['week'] ?? ''; + + if (isset($options['invalid_message'])) { + $yearOptions['invalid_message'] = $options['invalid_message']; + $weekOptions['invalid_message'] = $options['invalid_message']; + } + + if (isset($options['invalid_message_parameters'])) { + $yearOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + $weekOptions['invalid_message_parameters'] = $options['invalid_message_parameters']; + } + + if ('choice' === $options['widget']) { + // Only pass a subset of the options to children + $yearOptions['choices'] = array_combine($options['years'], $options['years']); + $yearOptions['placeholder'] = $options['placeholder']['year']; + $yearOptions['choice_translation_domain'] = $options['choice_translation_domain']['year']; + + $weekOptions['choices'] = array_combine($options['weeks'], $options['weeks']); + $weekOptions['placeholder'] = $options['placeholder']['week']; + $weekOptions['choice_translation_domain'] = $options['choice_translation_domain']['week']; + + // Append generic carry-along options + foreach (['required', 'translation_domain'] as $passOpt) { + $yearOptions[$passOpt] = $options[$passOpt]; + $weekOptions[$passOpt] = $options[$passOpt]; + } + } + + $builder->add('year', self::$widgets[$options['widget']], $yearOptions); + $builder->add('week', self::$widgets[$options['widget']], $weekOptions); + } + } + + /** + * {@inheritdoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['widget'] = $options['widget']; + + if ($options['html5']) { + $view->vars['type'] = 'week'; + } + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $compound = function (Options $options) { + return 'single_text' !== $options['widget']; + }; + + $placeholderDefault = function (Options $options) { + return $options['required'] ? null : ''; + }; + + $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) { + if (\is_array($placeholder)) { + $default = $placeholderDefault($options); + + return array_merge( + ['year' => $default, 'week' => $default], + $placeholder + ); + } + + return [ + 'year' => $placeholder, + 'week' => $placeholder, + ]; + }; + + $choiceTranslationDomainNormalizer = function (Options $options, $choiceTranslationDomain) { + if (\is_array($choiceTranslationDomain)) { + $default = false; + + return array_replace( + ['year' => $default, 'week' => $default], + $choiceTranslationDomain + ); + } + + return [ + 'year' => $choiceTranslationDomain, + 'week' => $choiceTranslationDomain, + ]; + }; + + $resolver->setDefaults([ + 'years' => range(date('Y') - 10, date('Y') + 10), + 'weeks' => array_combine(range(1, 53), range(1, 53)), + 'widget' => 'single_text', + 'input' => 'array', + 'placeholder' => $placeholderDefault, + 'html5' => static function (Options $options) { + return 'single_text' === $options['widget']; + }, + 'error_bubbling' => false, + 'empty_data' => function (Options $options) { + return $options['compound'] ? [] : ''; + }, + 'compound' => $compound, + 'choice_translation_domain' => false, + ]); + + $resolver->setNormalizer('placeholder', $placeholderNormalizer); + $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); + $resolver->setNormalizer('html5', function (Options $options, $html5) { + if ($html5 && 'single_text' !== $options['widget']) { + throw new LogicException(sprintf('The "widget" option of %s must be set to "single_text" when the "html5" option is enabled.', self::class)); + } + + return $html5; + }); + + $resolver->setAllowedValues('input', [ + 'string', + 'array', + ]); + + $resolver->setAllowedValues('widget', [ + 'single_text', + 'text', + 'choice', + ]); + + $resolver->setAllowedTypes('years', 'int[]'); + $resolver->setAllowedTypes('weeks', 'int[]'); + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix() + { + return 'week'; + } +} diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 8f1231e3bc..c565894ccc 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -2765,4 +2765,87 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase [true], ]; } + + public function testWeekSingleText() + { + $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ + 'input' => 'string', + 'widget' => 'single_text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/input + [@type="week"] + [@name="holidays"] + [@class="my&class"] + [@value="1970-W01"] +' + ); + } + + public function testWeekSingleTextNoHtml5() + { + $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ + 'input' => 'string', + 'widget' => 'single_text', + 'html5' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/input + [@type="text"] + [@name="holidays"] + [@class="my&class"] + [@value="1970-W01"] +' + ); + } + + public function testWeekChoices() + { + $data = ['year' => date('Y'), 'week' => 1]; + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ + 'input' => 'array', + 'required' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/div + [@class="my&class"] + [ + ./select + [@id="name_year"] + [./option[@value="'.$data['year'].'"][@selected="selected"]] + /following-sibling::select + [@id="name_week"] + [./option[@value="'.$data['week'].'"][@selected="selected"]] + ] + [count(.//select)=2]' + ); + } + + public function testWeekText() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ + 'input' => 'string', + 'widget' => 'text', + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/div + [@class="my&class"] + [ + ./input + [@id="name_year"] + [@type="number"] + [@value="2000"] + /following-sibling::input + [@id="name_week"] + [@type="number"] + [@value="1"] + ] + [count(./input)=2]' + ); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/WeekToArrayTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/WeekToArrayTransformerTest.php new file mode 100644 index 0000000000..3f681622a8 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/WeekToArrayTransformerTest.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Extension\Core\DataTransformer\WeekToArrayTransformer; + +class WeekToArrayTransformerTest extends TestCase +{ + public function testTransform() + { + $transformer = new WeekToArrayTransformer(); + + $this->assertSame(['year' => 2019, 'week' => 1], $transformer->transform('2019-W01')); + } + + public function testTransformEmpty() + { + $transformer = new WeekToArrayTransformer(); + + $this->assertSame(['year' => null, 'week' => null], $transformer->transform(null)); + } + + /** + * @dataProvider transformationFailuresProvider + */ + public function testTransformationFailures($input, string $message) + { + $this->expectException(TransformationFailedException::class); + $this->expectExceptionMessage($message); + + $transformer = new WeekToArrayTransformer(); + $transformer->transform($input); + } + + public function transformationFailuresProvider(): array + { + return [ + 'malformed string' => ['lorem', 'Given data does not follow the date format "Y-\WW".'], + 'non-string' => [[], 'Value is expected to be a string but was "array".'], + ]; + } + + public function testReverseTransform() + { + $transformer = new WeekToArrayTransformer(); + + $input = [ + 'year' => 2019, + 'week' => 1, + ]; + + $this->assertEquals('2019-W01', $transformer->reverseTransform($input)); + } + + public function testReverseTransformCompletelyEmpty() + { + $transformer = new WeekToArrayTransformer(); + + $input = [ + 'year' => null, + 'week' => null, + ]; + + $this->assertNull($transformer->reverseTransform($input)); + } + + public function testReverseTransformNull() + { + $transformer = new WeekToArrayTransformer(); + + $this->assertNull($transformer->reverseTransform(null)); + } + + public function testReverseTransformEmpty() + { + $transformer = new WeekToArrayTransformer(); + + $this->assertNull($transformer->reverseTransform([])); + } + + /** + * @dataProvider reverseTransformationFailuresProvider + */ + public function testReverseTransformFailures($input, string $message) + { + $this->expectException(TransformationFailedException::class); + $this->expectExceptionMessage($message); + + $transformer = new WeekToArrayTransformer(); + $transformer->reverseTransform($input); + } + + public function reverseTransformationFailuresProvider(): array + { + return [ + 'missing year' => [['week' => 1], 'Key "year" is missing.'], + 'missing week' => [['year' => 2019], 'Key "week" is missing.'], + 'integer instead of array' => [0, 'Value is expected to be an array, but was "integer"'], + 'string instead of array' => ['12345', 'Value is expected to be an array, but was "string"'], + 'week invalid' => [['year' => 2019, 'week' => 66], 'Week "66" does not exist for year "2019".'], + 'year null' => [['year' => null, 'week' => 1], 'Year is expected to be an integer, but was "NULL".'], + 'week null' => [['year' => 2019, 'week' => null], 'Week is expected to be an integer, but was "NULL".'], + 'year non-integer' => [['year' => '2019', 'week' => 1], 'Year is expected to be an integer, but was "string".'], + 'week non-integer' => [['year' => 2019, 'week' => '1'], 'Week is expected to be an integer, but was "string".'], + 'unexpected key' => [['year' => 2019, 'bar' => 'baz', 'week' => 1, 'foo' => 'foobar'], 'Expected only keys "year" and "week" to be present, but also got ["bar", "foo"].'], + ]; + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/WeekTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/WeekTypeTest.php new file mode 100644 index 0000000000..aea4c064b3 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/WeekTypeTest.php @@ -0,0 +1,323 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\Type; + +use Symfony\Component\Form\FormError; + +class WeekTypeTest extends BaseTypeTest +{ + const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\WeekType'; + + public function testSubmitArray() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', + 'input' => 'array', + ]); + + $form->submit([ + 'year' => '2019', + 'week' => '1', + ]); + + $this->assertSame(['year' => 2019, 'week' => 1], $form->getData()); + } + + public function testSubmitString() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'years' => [2019], + 'input' => 'string', + 'widget' => 'choice', + ]); + + $form->submit([ + 'year' => '2019', + 'week' => '1', + ]); + + $this->assertEquals('2019-W01', $form->getData()); + } + + public function testSubmitStringSingleText() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'years' => [2019], + 'input' => 'string', + 'widget' => 'single_text', + ]); + + $form->submit('2019-W01'); + + $this->assertEquals('2019-W01', $form->getData()); + } + + public function testPassDefaultPlaceholderToViewIfNotRequired() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'required' => false, + 'widget' => 'choice', + ]) + ->createView(); + + $this->assertSame('', $view['year']->vars['placeholder']); + $this->assertSame('', $view['week']->vars['placeholder']); + } + + public function testPassNoPlaceholderToViewIfRequired() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'required' => true, + 'widget' => 'choice', + ]) + ->createView(); + + $this->assertNull($view['year']->vars['placeholder']); + $this->assertNull($view['week']->vars['placeholder']); + } + + public function testPassPlaceholderAsString() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'placeholder' => 'Empty', + 'widget' => 'choice', + ]) + ->createView(); + + $this->assertSame('Empty', $view['year']->vars['placeholder']); + $this->assertSame('Empty', $view['week']->vars['placeholder']); + } + + public function testPassPlaceholderAsArray() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'placeholder' => [ + 'year' => 'Empty year', + 'week' => 'Empty week', + ], + 'widget' => 'choice', + ]) + ->createView(); + + $this->assertSame('Empty year', $view['year']->vars['placeholder']); + $this->assertSame('Empty week', $view['week']->vars['placeholder']); + } + + public function testPassPlaceholderAsPartialArrayAddEmptyIfNotRequired() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'required' => false, + 'placeholder' => [ + 'year' => 'Empty year', + ], + 'widget' => 'choice', + ]) + ->createView(); + + $this->assertSame('Empty year', $view['year']->vars['placeholder']); + $this->assertSame('', $view['week']->vars['placeholder']); + } + + public function testPassPlaceholderAsPartialArrayAddNullIfRequired() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'required' => true, + 'placeholder' => [ + 'year' => 'Empty year', + ], + 'widget' => 'choice', + ]) + ->createView(); + + $this->assertSame('Empty year', $view['year']->vars['placeholder']); + $this->assertNull($view['week']->vars['placeholder']); + } + + public function testPassHtml5TypeIfSingleTextAndHtml5Format() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'single_text', + ]) + ->createView(); + + $this->assertSame('week', $view->vars['type']); + } + + public function testDontPassHtml5TypeIfHtml5NotAllowed() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'single_text', + 'html5' => false, + ]) + ->createView(); + + $this->assertArrayNotHasKey('type', $view->vars); + } + + public function testDontPassHtml5TypeIfNotSingleText() + { + $view = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'text', + ]) + ->createView(); + + $this->assertArrayNotHasKey('type', $view->vars); + } + + public function testYearTypeChoiceErrorsBubbleUp() + { + $error = new FormError('Invalid!'); + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', + ]); + + $form['year']->addError($error); + + $this->assertSame([], iterator_to_array($form['year']->getErrors())); + $this->assertSame([$error], iterator_to_array($form->getErrors())); + } + + public function testWeekTypeChoiceErrorsBubbleUp() + { + $error = new FormError('Invalid!'); + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', + ]); + + $form['week']->addError($error); + + $this->assertSame([], iterator_to_array($form['week']->getErrors())); + $this->assertSame([$error], iterator_to_array($form->getErrors())); + } + + public function testPassDefaultChoiceTranslationDomain() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', + ]); + + $view = $form->createView(); + + $this->assertFalse($view['year']->vars['choice_translation_domain']); + $this->assertFalse($view['week']->vars['choice_translation_domain']); + } + + public function testPassChoiceTranslationDomainAsString() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'choice_translation_domain' => 'messages', + 'widget' => 'choice', + ]); + + $view = $form->createView(); + $this->assertSame('messages', $view['year']->vars['choice_translation_domain']); + $this->assertSame('messages', $view['week']->vars['choice_translation_domain']); + } + + public function testPassChoiceTranslationDomainAsArray() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'choice_translation_domain' => [ + 'year' => 'foo', + 'week' => 'test', + ], + 'widget' => 'choice', + ]); + + $view = $form->createView(); + $this->assertSame('foo', $view['year']->vars['choice_translation_domain']); + $this->assertSame('test', $view['week']->vars['choice_translation_domain']); + } + + public function testSubmitNull($expected = null, $norm = null, $view = null) + { + $form = $this->factory->create($this->getTestedType(), null, [ + 'widget' => 'choice', + ]); + $form->submit(null); + + $this->assertSame(['year' => null, 'week' => null], $form->getData()); + $this->assertSame(['year' => null, 'week' => null], $form->getNormData()); + $this->assertSame(['year' => null, 'week' => null], $form->getViewData()); + } + + public function testSubmitFromChoiceEmpty() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'choice', + 'required' => false, + ]); + + $form->submit([ + 'year' => '', + 'week' => '', + ]); + + $this->assertSame(['year' => null, 'week' => null], $form->getData()); + } + + public function testSubmitNullWithText() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'text', + ]); + $form->submit(null); + + $this->assertSame(['year' => null, 'week' => null], $form->getViewData()); + } + + public function testSubmitNullWithSingleText() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => 'single_text', + 'input' => 'string', + ]); + $form->submit(null); + + $this->assertNull($form->getData()); + $this->assertNull($form->getNormData()); + $this->assertSame('', $form->getViewData()); + } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = [], $expectedData = null) + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'empty_data' => $emptyData, + 'widget' => 'choice', + ]); + $form->submit(null); + + $this->assertSame(['year' => null, 'week' => null], $form->getData()); + } + + /** + * @dataProvider provideEmptyData + */ + public function testSubmitNullUsesDateEmptyDataString($widget, $emptyData, $expectedData) + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'widget' => $widget, + 'empty_data' => $emptyData, + ]); + $form->submit(null); + + $this->assertSame($expectedData, $form->getData()); + } + + public function provideEmptyData() + { + return [ + 'Compound text field' => ['text', ['year' => '2019', 'week' => '1'], ['year' => 2019, 'week' => 1]], + 'Compound choice field' => ['choice', ['year' => '2019', 'week' => '1'], ['year' => 2019, 'week' => 1]], + ]; + } +}