Initial commit for Bootstrap 4 form theme, based on Bootstrap 3 form theme

Fixed Bootstrap 4 error output

Updated form errors to look correctly

Cranked Twig Bridge Composer version to ~3.2

Added @ author PHPdoc tag to BS 4 test classes

Fixed small items, and added fieldset/legend support

- Fixed form class for File type
- Added fieldset element for expanded form types
- Added legend element as label for expanded form types
- Fixed horizontal form classes for labels

Added test for legend form label

Fixed form label coloring on validation errors

Fixed concrete Bootstrap layout test classes to use new code

Fixed tests for form-control-label class on form labels

Fixed a typo in using old Bootstrap 4 concrete test code

Processed proposed changes from stof

Processed proposed changes in bootstrap base layout from stof

Updated margin-top for error list in the alpha-5 version of BS 4

Fixed {% endblock ... %} and fixed BS error class in test

Fixed TwigRenderer => FormRenderer code change

Minor fixed for radio/checkboxes and fixed validation feedback

Added ~3.4 require of symfony/form (and <3.4 conflict) to Twig Bridge
composer.json

Updated Boostrap 4 file input to have class .form-control-file
This commit is contained in:
Hidde Wieringa 2017-02-24 23:34:35 +01:00
parent 0beb598a7b
commit 4222d54a53
No known key found for this signature in database
GPG Key ID: 014B9FCF5F3BFA95
12 changed files with 1786 additions and 163 deletions

View File

@ -34,26 +34,6 @@ col-sm-2
{##}</div>
{%- endblock form_row %}
{% block checkbox_row -%}
{{- block('checkbox_radio_row') -}}
{%- endblock checkbox_row %}
{% block radio_row -%}
{{- block('checkbox_radio_row') -}}
{%- endblock radio_row %}
{% block checkbox_radio_row -%}
{% spaceless %}
<div class="form-group{% if not valid %} has-error{% endif %}">
<div class="{{ block('form_label_class') }}"></div>
<div class="{{ block('form_group_class') }}">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
{% endspaceless %}
{%- endblock checkbox_radio_row %}
{% block submit_row -%}
{% spaceless %}
<div class="form-group">

View File

@ -1,4 +1,4 @@
{% use "form_div_layout.html.twig" %}
{% use "bootstrap_base_layout.html.twig" %}
{# Widgets #}
@ -9,146 +9,10 @@
{{- parent() -}}
{%- endblock form_widget_simple %}
{% block textarea_widget -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
{{- parent() -}}
{%- endblock textarea_widget %}
{% block button_widget -%}
{% set attr = attr|merge({class: (attr.class|default('btn-default') ~ ' btn')|trim}) %}
{{- parent() -}}
{%- endblock %}
{% block money_widget -%}
<div class="input-group">
{% set append = money_pattern starts with '{{' %}
{% if not append %}
<span class="input-group-addon">{{ money_pattern|replace({ '{{ widget }}':''}) }}</span>
{% endif %}
{{- block('form_widget_simple') -}}
{% if append %}
<span class="input-group-addon">{{ money_pattern|replace({ '{{ widget }}':''}) }}</span>
{% endif %}
</div>
{%- endblock money_widget %}
{% block percent_widget -%}
<div class="input-group">
{{- block('form_widget_simple') -}}
<span class="input-group-addon">%</span>
</div>
{%- endblock percent_widget %}
{% block datetime_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form.date) -}}
{{- form_errors(form.time) -}}
{{- form_widget(form.date, { datetime: true } ) -}}
{{- form_widget(form.time, { datetime: true } ) -}}
</div>
{%- endif %}
{%- endblock datetime_widget %}
{% block date_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
{% if datetime is not defined or not datetime -%}
<div {{ block('widget_container_attributes') -}}>
{%- endif %}
{{- date_pattern|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
})|raw -}}
{% if datetime is not defined or not datetime -%}
</div>
{%- endif -%}
{% endif %}
{%- endblock date_widget %}
{% block time_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
{% if datetime is not defined or false == datetime -%}
<div {{ block('widget_container_attributes') -}}>
{%- endif -%}
{{- form_widget(form.hour) }}{% if with_minutes %}:{{ form_widget(form.minute) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second) }}{% endif %}
{% if datetime is not defined or false == datetime -%}
</div>
{%- endif -%}
{% endif %}
{%- endblock time_widget %}
{%- block dateinterval_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form) -}}
<div class="table-responsive">
<table class="table {{ table_class|default('table-bordered table-condensed table-striped') }}">
<thead>
<tr>
{%- if with_years %}<th>{{ form_label(form.years) }}</th>{% endif -%}
{%- if with_months %}<th>{{ form_label(form.months) }}</th>{% endif -%}
{%- if with_weeks %}<th>{{ form_label(form.weeks) }}</th>{% endif -%}
{%- if with_days %}<th>{{ form_label(form.days) }}</th>{% endif -%}
{%- if with_hours %}<th>{{ form_label(form.hours) }}</th>{% endif -%}
{%- if with_minutes %}<th>{{ form_label(form.minutes) }}</th>{% endif -%}
{%- if with_seconds %}<th>{{ form_label(form.seconds) }}</th>{% endif -%}
</tr>
</thead>
<tbody>
<tr>
{%- if with_years %}<td>{{ form_widget(form.years) }}</td>{% endif -%}
{%- if with_months %}<td>{{ form_widget(form.months) }}</td>{% endif -%}
{%- if with_weeks %}<td>{{ form_widget(form.weeks) }}</td>{% endif -%}
{%- if with_days %}<td>{{ form_widget(form.days) }}</td>{% endif -%}
{%- if with_hours %}<td>{{ form_widget(form.hours) }}</td>{% endif -%}
{%- if with_minutes %}<td>{{ form_widget(form.minutes) }}</td>{% endif -%}
{%- if with_seconds %}<td>{{ form_widget(form.seconds) }}</td>{% endif -%}
</tr>
</tbody>
</table>
</div>
{%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%}
</div>
{%- endif -%}
{%- endblock dateinterval_widget -%}
{% block choice_widget_collapsed -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
{{- parent() -}}
{%- endblock %}
{% block choice_widget_expanded -%}
{% if '-inline' in label_attr.class|default('') -%}
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{% endfor -%}
{%- else -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{% endfor -%}
</div>
{%- endif %}
{%- endblock choice_widget_expanded %}
{%- endblock button_widget %}
{% block checkbox_widget -%}
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}

View File

@ -0,0 +1,69 @@
{% use "bootstrap_4_layout.html.twig" %}
{# Labels #}
{% block form_label -%}
{%- if label is same as(false) -%}
<div class="{{ block('form_label_class') }}"></div>
{%- else -%}
{%- if expanded is not defined or not expanded -%}
{%- set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' col-form-label')|trim}) -%}
{%- endif -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%}
{{- parent() -}}
{%- endif -%}
{%- endblock form_label %}
{% block form_label_class -%}
col-sm-2
{%- endblock form_label_class %}
{# Rows #}
{% block form_row -%}
{%- if expanded is defined and expanded -%}
{{ block('fieldset_form_row') }}
{%- else -%}
<div class="form-group row{% if (not compound or force_error|default(false)) and not valid %} is-invalid{% endif %}">
{{- form_label(form) -}}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>
{##}</div>
{%- endif -%}
{%- endblock form_row %}
{% block fieldset_form_row -%}
<fieldset class="form-group">
<div class="row{% if (not compound or force_error|default(false)) and not valid %} is-invalid{% endif %}">
{{- form_label(form) -}}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>
</div>
{##}</fieldset>
{%- endblock fieldset_form_row %}
{% block submit_row -%}
<div class="form-group row">
<div class="{{ block('form_label_class') }}"></div>
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
</div>
</div>
{%- endblock submit_row %}
{% block reset_row -%}
<div class="form-group row">
<div class="{{ block('form_label_class') }}"></div>
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
</div>
</div>
{%- endblock reset_row %}
{% block form_group_class -%}
col-sm-10
{%- endblock form_group_class %}

View File

@ -0,0 +1,132 @@
{% use "bootstrap_base_layout.html.twig" %}
{# Widgets #}
{% block form_widget_simple -%}
{% if type is not defined or type != 'hidden' %}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control' ~ (type|default('') == 'file' ? '-file' : ''))|trim}) -%}
{% endif %}
{{- parent() -}}
{%- endblock form_widget_simple %}
{%- block widget_attributes -%}
{%- if not valid %}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %}
{% endif -%}
{{ parent() }}
{%- endblock widget_attributes -%}
{% block button_widget -%}
{%- set attr = attr|merge({class: (attr.class|default('btn-secondary') ~ ' btn')|trim}) -%}
{{- parent() -}}
{%- endblock button_widget %}
{% block checkbox_widget -%}
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}
{%- set attr = attr|merge({class: attr.class|default('form-check-input')}) -%}
{%- if 'checkbox-inline' in parent_label_class -%}
{{- form_label(form, null, { widget: parent() }) -}}
{%- else -%}
<div class="form-check">
{{- form_label(form, null, { widget: parent() }) -}}
</div>
{%- endif -%}
{%- endblock checkbox_widget %}
{% block radio_widget -%}
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}
{%- set attr = attr|merge({class: attr.class|default('form-check-input')}) -%}
{%- if 'radio-inline' in parent_label_class -%}
{{- form_label(form, null, { widget: parent() }) -}}
{%- else -%}
<div class="form-check">
{{- form_label(form, null, { widget: parent() }) -}}
</div>
{%- endif -%}
{%- endblock radio_widget %}
{% block choice_widget_expanded -%}
{% if '-inline' in label_attr.class|default('') -%}
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
valid: valid,
}) -}}
{% endfor -%}
{%- else -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
valid: valid,
}) -}}
{% endfor -%}
</div>
{%- endif %}
{%- endblock choice_widget_expanded %}
{# Labels #}
{% block form_label -%}
{%- if expanded is defined and expanded -%}
{%- set element = 'legend' -%}
{%- set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' col-form-legend')|trim}) -%}
{%- endif -%}
{%- set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' form-control-label')|trim}) -%}
{{- parent() -}}
{%- endblock form_label %}
{% block checkbox_radio_label -%}
{#- Do not display the label if widget is not defined in order to prevent double label rendering -#}
{%- if widget is defined -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%}
{%- if required -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%}
{%- endif -%}
{%- if parent_label_class is defined -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|trim}) -%}
{%- endif -%}
{%- if label is not same as(false) and label is empty -%}
{%- if label_format is not empty -%}
{%- set label = label_format|replace({
'%name%': name,
'%id%': id,
}) -%}
{%- else -%}
{%- set label = name|humanize -%}
{%- endif -%}
{%- endif -%}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}}
</label>
{%- endif -%}
{%- endblock checkbox_radio_label %}
{# Rows #}
{% block form_row -%}
{%- if expanded is defined and expanded -%}
{%- set element = 'fieldset' -%}
{%- endif -%}
<{{ element|default('div') }} class="form-group">
{{- form_label(form) -}}
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</{{ element|default('div') }}>
{%- endblock form_row %}
{# Errors #}
{% block form_errors -%}
{%- if errors|length > 0 -%}
<div class="{% if form.parent %}invalid-feedback{% else %}alert alert-danger{% endif %}">
<ul class="list-unstyled mb-0">
{%- for error in errors -%}
<li>{{ error.message }}</li>
{%- endfor -%}
</ul>
</div>
{%- endif %}
{%- endblock form_errors %}

View File

@ -0,0 +1,183 @@
{% use "form_div_layout.html.twig" %}
{# Widgets #}
{% block textarea_widget -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
{{- parent() -}}
{%- endblock textarea_widget %}
{% block money_widget -%}
<div class="input-group">
{%- set append = money_pattern starts with '{{' -%}
{%- if not append -%}
<span class="input-group-addon">{{ money_pattern|replace({ '{{ widget }}':''}) }}</span>
{%- endif -%}
{{- block('form_widget_simple') -}}
{%- if append -%}
<span class="input-group-addon">{{ money_pattern|replace({ '{{ widget }}':''}) }}</span>
{%- endif -%}
</div>
{%- endblock money_widget %}
{% block percent_widget -%}
<div class="input-group">
{{- block('form_widget_simple') -}}
<span class="input-group-addon">%</span>
</div>
{%- endblock percent_widget %}
{% block datetime_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form.date) -}}
{{- form_errors(form.time) -}}
{{- form_widget(form.date, { datetime: true } ) -}}
{{- form_widget(form.time, { datetime: true } ) -}}
</div>
{%- endif -%}
{%- endblock datetime_widget %}
{% block date_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
{%- if datetime is not defined or not datetime -%}
<div {{ block('widget_container_attributes') -}}>
{%- endif %}
{{- date_pattern|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
})|raw -}}
{%- if datetime is not defined or not datetime -%}
</div>
{%- endif -%}
{%- endif -%}
{%- endblock date_widget %}
{% block time_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
{%- if datetime is not defined or false == datetime -%}
<div {{ block('widget_container_attributes') -}}>
{%- endif -%}
{{- form_widget(form.hour) }}{% if with_minutes %}:{{ form_widget(form.minute) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second) }}{% endif %}
{%- if datetime is not defined or false == datetime -%}
</div>
{%- endif -%}
{%- endif -%}
{%- endblock time_widget %}
{%- block dateinterval_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form) -}}
<div class="table-responsive">
<table class="table {{ table_class|default('table-bordered table-condensed table-striped') }}">
<thead>
<tr>
{%- if with_years %}<th>{{ form_label(form.years) }}</th>{% endif -%}
{%- if with_months %}<th>{{ form_label(form.months) }}</th>{% endif -%}
{%- if with_weeks %}<th>{{ form_label(form.weeks) }}</th>{% endif -%}
{%- if with_days %}<th>{{ form_label(form.days) }}</th>{% endif -%}
{%- if with_hours %}<th>{{ form_label(form.hours) }}</th>{% endif -%}
{%- if with_minutes %}<th>{{ form_label(form.minutes) }}</th>{% endif -%}
{%- if with_seconds %}<th>{{ form_label(form.seconds) }}</th>{% endif -%}
</tr>
</thead>
<tbody>
<tr>
{%- if with_years %}<td>{{ form_widget(form.years) }}</td>{% endif -%}
{%- if with_months %}<td>{{ form_widget(form.months) }}</td>{% endif -%}
{%- if with_weeks %}<td>{{ form_widget(form.weeks) }}</td>{% endif -%}
{%- if with_days %}<td>{{ form_widget(form.days) }}</td>{% endif -%}
{%- if with_hours %}<td>{{ form_widget(form.hours) }}</td>{% endif -%}
{%- if with_minutes %}<td>{{ form_widget(form.minutes) }}</td>{% endif -%}
{%- if with_seconds %}<td>{{ form_widget(form.seconds) }}</td>{% endif -%}
</tr>
</tbody>
</table>
</div>
{%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%}
</div>
{%- endif -%}
{%- endblock dateinterval_widget -%}
{% block choice_widget_collapsed -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%}
{{- parent() -}}
{%- endblock choice_widget_collapsed %}
{% block choice_widget_expanded -%}
{%- if '-inline' in label_attr.class|default('') -%}
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{% endfor -%}
{%- else -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{%- endfor -%}
</div>
{%- endif -%}
{%- endblock choice_widget_expanded %}
{# Labels #}
{% block choice_label -%}
{# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #}
{%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%}
{{- block('form_label') -}}
{% endblock choice_label %}
{% block checkbox_label -%}
{{- block('checkbox_radio_label') -}}
{%- endblock checkbox_label %}
{% block radio_label -%}
{{- block('checkbox_radio_label') -}}
{%- endblock radio_label %}
{# Rows #}
{% block button_row -%}
<div class="form-group">
{{- form_widget(form) -}}
</div>
{%- endblock button_row %}
{% block choice_row -%}
{%- set force_error = true -%}
{{- block('form_row') -}}
{%- endblock choice_row %}
{% block date_row -%}
{%- set force_error = true -%}
{{- block('form_row') -}}
{%- endblock date_row %}
{% block time_row -%}
{%- set force_error = true -%}
{{- block('form_row') -}}
{%- endblock time_row %}
{% block datetime_row -%}
{%- set force_error = true -%}
{{- block('form_row') -}}
{%- endblock datetime_row %}

View File

@ -259,7 +259,7 @@
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
<label{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}>{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}</label>
<{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}>{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}</{{ element|default('label') }}>
{%- endif -%}
{%- endblock form_label -%}

View File

@ -20,7 +20,7 @@
{% block button_widget -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %}
{{- parent() -}}
{%- endblock %}
{%- endblock button_widget %}
{% block money_widget -%}
<div class="row collapse">
@ -228,7 +228,7 @@
{# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #}
{% set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) %}
{{- block('form_label') -}}
{%- endblock %}
{%- endblock choice_label %}
{% block checkbox_label -%}
{{- block('checkbox_radio_label') -}}

View File

@ -0,0 +1,107 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Tests\AbstractBootstrap4HorizontalLayoutTest;
/**
* Class providing test cases for the Bootstrap 4 Twig form theme.
*
* @author Hidde Wieringa <hidde@hiddewieringa.nl>
*/
class FormExtensionBootstrap4HorizontalLayoutTest extends AbstractBootstrap4HorizontalLayoutTest
{
use RuntimeLoaderProvider;
protected $testableFeatures = array(
'choice_attr',
);
private $renderer;
protected function setUp()
{
parent::setUp();
$loader = new StubFilesystemLoader(array(
__DIR__.'/../../Resources/views/Form',
__DIR__.'/Fixtures/templates/form',
));
$environment = new \Twig_Environment($loader, array('strict_variables' => true));
$environment->addExtension(new TranslationExtension(new StubTranslator()));
$environment->addExtension(new FormExtension());
$rendererEngine = new TwigRendererEngine(array(
'bootstrap_4_horizontal_layout.html.twig',
'custom_widgets.html.twig',
), $environment);
$this->renderer = new FormRenderer($rendererEngine, $this->getMockBuilder('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')->getMock());
$this->registerTwigRuntimeLoader($environment, $this->renderer);
}
protected function renderForm(FormView $view, array $vars = array())
{
return (string) $this->renderer->renderBlock($view, 'form', $vars);
}
protected function renderLabel(FormView $view, $label = null, array $vars = array())
{
if ($label !== null) {
$vars += array('label' => $label);
}
return (string) $this->renderer->searchAndRenderBlock($view, 'label', $vars);
}
protected function renderErrors(FormView $view)
{
return (string) $this->renderer->searchAndRenderBlock($view, 'errors');
}
protected function renderWidget(FormView $view, array $vars = array())
{
return (string) $this->renderer->searchAndRenderBlock($view, 'widget', $vars);
}
protected function renderRow(FormView $view, array $vars = array())
{
return (string) $this->renderer->searchAndRenderBlock($view, 'row', $vars);
}
protected function renderRest(FormView $view, array $vars = array())
{
return (string) $this->renderer->searchAndRenderBlock($view, 'rest', $vars);
}
protected function renderStart(FormView $view, array $vars = array())
{
return (string) $this->renderer->renderBlock($view, 'form_start', $vars);
}
protected function renderEnd(FormView $view, array $vars = array())
{
return (string) $this->renderer->renderBlock($view, 'form_end', $vars);
}
protected function setTheme(FormView $view, array $themes)
{
$this->renderer->setTheme($view, $themes);
}
}

View File

@ -0,0 +1,129 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Tests\AbstractBootstrap4LayoutTest;
/**
* Class providing test cases for the Bootstrap 4 horizontal Twig form theme.
*
* @author Hidde Wieringa <hidde@hiddewieringa.nl>
*/
class FormExtensionBootstrap4LayoutTest extends AbstractBootstrap4LayoutTest
{
use RuntimeLoaderProvider;
/**
* @var FormRenderer;
*/
private $renderer;
protected function setUp()
{
parent::setUp();
$loader = new StubFilesystemLoader(array(
__DIR__.'/../../Resources/views/Form',
__DIR__.'/Fixtures/templates/form',
));
$environment = new \Twig_Environment($loader, array('strict_variables' => true));
$environment->addExtension(new TranslationExtension(new StubTranslator()));
$environment->addExtension(new FormExtension());
$rendererEngine = new TwigRendererEngine(array(
'bootstrap_4_layout.html.twig',
'custom_widgets.html.twig',
), $environment);
$this->renderer = new FormRenderer($rendererEngine, $this->getMockBuilder('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')->getMock());
$this->registerTwigRuntimeLoader($environment, $this->renderer);
}
public function testStartTagHasNoActionAttributeWhenActionIsEmpty()
{
$form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array(
'method' => 'get',
'action' => '',
));
$html = $this->renderStart($form->createView());
$this->assertSame('<form name="form" method="get">', $html);
}
public function testStartTagHasActionAttributeWhenActionIsZero()
{
$form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array(
'method' => 'get',
'action' => '0',
));
$html = $this->renderStart($form->createView());
$this->assertSame('<form name="form" method="get" action="0">', $html);
}
protected function renderForm(FormView $view, array $vars = array())
{
return (string) $this->renderer->renderBlock($view, 'form', $vars);
}
protected function renderLabel(FormView $view, $label = null, array $vars = array())
{
if ($label !== null) {
$vars += array('label' => $label);
}
return (string) $this->renderer->searchAndRenderBlock($view, 'label', $vars);
}
protected function renderErrors(FormView $view)
{
return (string) $this->renderer->searchAndRenderBlock($view, 'errors');
}
protected function renderWidget(FormView $view, array $vars = array())
{
return (string) $this->renderer->searchAndRenderBlock($view, 'widget', $vars);
}
protected function renderRow(FormView $view, array $vars = array())
{
return (string) $this->renderer->searchAndRenderBlock($view, 'row', $vars);
}
protected function renderRest(FormView $view, array $vars = array())
{
return (string) $this->renderer->searchAndRenderBlock($view, 'rest', $vars);
}
protected function renderStart(FormView $view, array $vars = array())
{
return (string) $this->renderer->renderBlock($view, 'form_start', $vars);
}
protected function renderEnd(FormView $view, array $vars = array())
{
return (string) $this->renderer->renderBlock($view, 'form_end', $vars);
}
protected function setTheme(FormView $view, array $themes)
{
$this->renderer->setTheme($view, $themes);
}
}

View File

@ -23,7 +23,7 @@
"fig/link-util": "^1.0",
"symfony/asset": "~2.8|~3.0|~4.0",
"symfony/finder": "~2.8|~3.0|~4.0",
"symfony/form": "^3.2.10|^3.3.3|~4.0",
"symfony/form": "~3.4|~4.0",
"symfony/http-kernel": "~3.2|~4.0",
"symfony/polyfill-intl-icu": "~1.0",
"symfony/routing": "~2.8|~3.0|~4.0",
@ -39,7 +39,7 @@
"symfony/web-link": "~3.3|~4.0"
},
"conflict": {
"symfony/form": "<3.2.10|~3.3,<3.3.3",
"symfony/form": "<3.4",
"symfony/console": "<3.4"
},
"suggest": {

View File

@ -0,0 +1,181 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests;
/**
* Abstract class providing test cases for the Bootstrap 4 horizontal Twig form theme.
*
* @author Hidde Wieringa <hidde@hiddewieringa.nl>
*/
abstract class AbstractBootstrap4HorizontalLayoutTest extends AbstractBootstrap4LayoutTest
{
public function testLabelOnForm()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType');
$view = $form->createView();
$this->renderWidget($view, array('label' => 'foo'));
$html = $this->renderLabel($view);
$this->assertMatchesXpath($html,
'/label
[@class="col-form-label col-sm-2 form-control-label required"]
[.="[trans]Name[/trans]"]
'
);
}
public function testLabelDoesNotRenderFieldAttributes()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType');
$html = $this->renderLabel($form->createView(), null, array(
'attr' => array(
'class' => 'my&class',
),
));
$this->assertMatchesXpath($html,
'/label
[@for="name"]
[@class="col-form-label col-sm-2 form-control-label required"]
'
);
}
public function testLabelWithCustomAttributesPassedDirectly()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType');
$html = $this->renderLabel($form->createView(), null, array(
'label_attr' => array(
'class' => 'my&class',
),
));
$this->assertMatchesXpath($html,
'/label
[@for="name"]
[@class="my&class col-form-label col-sm-2 form-control-label required"]
'
);
}
public function testLabelWithCustomTextAndCustomAttributesPassedDirectly()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType');
$html = $this->renderLabel($form->createView(), 'Custom label', array(
'label_attr' => array(
'class' => 'my&class',
),
));
$this->assertMatchesXpath($html,
'/label
[@for="name"]
[@class="my&class col-form-label col-sm-2 form-control-label required"]
[.="[trans]Custom label[/trans]"]
'
);
}
public function testLabelWithCustomTextAsOptionAndCustomAttributesPassedDirectly()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, array(
'label' => 'Custom label',
));
$html = $this->renderLabel($form->createView(), null, array(
'label_attr' => array(
'class' => 'my&class',
),
));
$this->assertMatchesXpath($html,
'/label
[@for="name"]
[@class="my&class col-form-label col-sm-2 form-control-label required"]
[.="[trans]Custom label[/trans]"]
'
);
}
public function testLegendOnExpandedType()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
'label' => 'Custom label',
'expanded' => true,
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
));
$view = $form->createView();
$this->renderWidget($view);
$html = $this->renderLabel($view);
$this->assertMatchesXpath($html,
'/legend
[@class="col-sm-2 col-form-legend form-control-label required"]
[.="[trans]Custom label[/trans]"]
'
);
}
public function testStartTag()
{
$form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array(
'method' => 'get',
'action' => 'http://example.com/directory',
));
$html = $this->renderStart($form->createView());
$this->assertSame('<form name="form" method="get" action="http://example.com/directory">', $html);
}
public function testStartTagWithOverriddenVars()
{
$form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array(
'method' => 'put',
'action' => 'http://example.com/directory',
));
$html = $this->renderStart($form->createView(), array(
'method' => 'post',
'action' => 'http://foo.com/directory',
));
$this->assertSame('<form name="form" method="post" action="http://foo.com/directory">', $html);
}
public function testStartTagForMultipartForm()
{
$form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, array(
'method' => 'get',
'action' => 'http://example.com/directory',
))
->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType')
->getForm();
$html = $this->renderStart($form->createView());
$this->assertSame('<form name="form" method="get" action="http://example.com/directory" enctype="multipart/form-data">', $html);
}
public function testStartTagWithExtraAttributes()
{
$form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array(
'method' => 'get',
'action' => 'http://example.com/directory',
));
$html = $this->renderStart($form->createView(), array(
'attr' => array('class' => 'foobar'),
));
$this->assertSame('<form name="form" method="get" action="http://example.com/directory" class="foobar">', $html);
}
}

View File

@ -0,0 +1,978 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests;
use Symfony\Component\Form\FormError;
/**
* Abstract class providing test cases for the Bootstrap 4 Twig form theme.
*
* @author Hidde Wieringa <hidde@hiddewieringa.nl>
*/
abstract class AbstractBootstrap4LayoutTest extends AbstractBootstrap3LayoutTest
{
public function testLabelOnForm()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType');
$view = $form->createView();
$this->renderWidget($view, array('label' => 'foo'));
$html = $this->renderLabel($view);
$this->assertMatchesXpath($html,
'/label
[@class="form-control-label required"]
[.="[trans]Name[/trans]"]
'
);
}
public function testLabelDoesNotRenderFieldAttributes()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType');
$html = $this->renderLabel($form->createView(), null, array(
'attr' => array(
'class' => 'my&class',
),
));
$this->assertMatchesXpath($html,
'/label
[@for="name"]
[@class="form-control-label required"]
'
);
}
public function testLabelWithCustomAttributesPassedDirectly()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType');
$html = $this->renderLabel($form->createView(), null, array(
'label_attr' => array(
'class' => 'my&class',
),
));
$this->assertMatchesXpath($html,
'/label
[@for="name"]
[@class="my&class form-control-label required"]
'
);
}
public function testLabelWithCustomTextAndCustomAttributesPassedDirectly()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType');
$html = $this->renderLabel($form->createView(), 'Custom label', array(
'label_attr' => array(
'class' => 'my&class',
),
));
$this->assertMatchesXpath($html,
'/label
[@for="name"]
[@class="my&class form-control-label required"]
[.="[trans]Custom label[/trans]"]
'
);
}
public function testLabelWithCustomTextAsOptionAndCustomAttributesPassedDirectly()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, array(
'label' => 'Custom label',
));
$html = $this->renderLabel($form->createView(), null, array(
'label_attr' => array(
'class' => 'my&class',
),
));
$this->assertMatchesXpath($html,
'/label
[@for="name"]
[@class="my&class form-control-label required"]
[.="[trans]Custom label[/trans]"]
'
);
}
public function testLegendOnExpandedType()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
'label' => 'Custom label',
'expanded' => true,
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
));
$view = $form->createView();
$this->renderWidget($view);
$html = $this->renderLabel($view);
$this->assertMatchesXpath($html,
'/legend
[@class="col-form-legend form-control-label required"]
[.="[trans]Custom label[/trans]"]
'
);
}
public function testErrors()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType');
$form->addError(new FormError('[trans]Error 1[/trans]'));
$form->addError(new FormError('[trans]Error 2[/trans]'));
$view = $form->createView();
$html = $this->renderErrors($view);
$this->assertMatchesXpath($html,
'/div
[@class="alert alert-danger"]
[
./ul
[@class="list-unstyled mb-0"]
[
./li
[.="[trans]Error 1[/trans]"]
/following-sibling::li
[.="[trans]Error 2[/trans]"]
]
[count(./li)=2]
]
'
);
}
public function testCheckedCheckbox()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', true);
$this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')),
'/div
[@class="form-check"]
[
./label
[.=" [trans]Name[/trans]"]
[@class="form-check-label required"]
[
./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class"][@checked="checked"][@value="1"]
]
]
'
);
}
public function testSingleChoiceAttributesWithMainAttributes()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'multiple' => false,
'expanded' => false,
'attr' => array('class' => 'bar&baz'),
));
$this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')),
'/select
[@name="name"]
[@class="bar&baz form-control"]
[not(@required)]
[
./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=2]
'
);
}
public function testSingleExpandedChoiceAttributesWithMainAttributes()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'multiple' => false,
'expanded' => true,
'attr' => array('class' => 'bar&baz'),
));
$this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')),
'/div
[@class="bar&baz"]
[
./div
[@class="form-check"]
[
./label
[.=" [trans]Choice&A[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&B[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testUncheckedCheckbox()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', false);
$this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')),
'/div
[@class="form-check"]
[
./label
[.=" [trans]Name[/trans]"]
[
./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class"][not(@checked)]
]
]
'
);
}
public function testCheckboxWithValue()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', false, array(
'value' => 'foo&bar',
));
$this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')),
'/div
[@class="form-check"]
[
./label
[.=" [trans]Name[/trans]"]
[
./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class"][@value="foo&bar"]
]
]
'
);
}
public function testSingleChoiceExpanded()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" [trans]Choice&A[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&B[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testSingleChoiceExpandedWithLabelsAsFalse()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choice_label' => false,
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testSingleChoiceExpandedWithLabelsSetByCallable()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'choice_label' => function ($choice, $label, $value) {
if ('&b' === $choice) {
return false;
}
return 'label.'.$value;
},
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" [trans]label.&a[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]label.&c[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_2"][@value="&c"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testSingleChoiceExpandedWithLabelsSetFalseByCallable()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choice_label' => function () {
return false;
},
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testSingleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'multiple' => false,
'expanded' => true,
'choice_translation_domain' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" Choice&A"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" Choice&B"]
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testSingleChoiceExpandedAttributes()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')),
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" [trans]Choice&A[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&B[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)][@class="foo&bar"]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testSingleChoiceExpandedWithPlaceholder()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'multiple' => false,
'expanded' => true,
'placeholder' => 'Test&Me',
'required' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" [trans]Test&Me[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&A[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&B[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'multiple' => false,
'expanded' => true,
'required' => false,
'choice_translation_domain' => false,
'placeholder' => 'Placeholder&Not&Translated',
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" Placeholder&Not&Translated"]
[
./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" Choice&A"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" Choice&B"]
[
./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testSingleChoiceExpandedWithBooleanValue()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', true, array(
'choices' => array('Choice&A' => '1', 'Choice&B' => '0'),
'multiple' => false,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" [trans]Choice&A[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&B[/trans]"]
[
./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testMultipleChoiceExpanded()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'multiple' => true,
'expanded' => true,
'required' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" [trans]Choice&A[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&B[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&C[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testMultipleChoiceExpandedWithLabelsAsFalse()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choice_label' => false,
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testMultipleChoiceExpandedWithLabelsSetByCallable()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'choice_label' => function ($choice, $label, $value) {
if ('&b' === $choice) {
return false;
}
return 'label.'.$value;
},
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" [trans]label.&a[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]label.&c[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_2"][@value="&c"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'),
'choice_label' => function () {
return false;
},
'multiple' => true,
'expanded' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testMultipleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'multiple' => true,
'expanded' => true,
'required' => true,
'choice_translation_domain' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" Choice&A"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" Choice&B"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" Choice&C"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testMultipleChoiceExpandedAttributes()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array(
'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'),
'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')),
'multiple' => true,
'expanded' => true,
'required' => true,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="form-check"]
[
./label
[.=" [trans]Choice&A[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&B[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)][@class="foo&bar"]
]
]
/following-sibling::div
[@class="form-check"]
[
./label
[.=" [trans]Choice&C[/trans]"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
'
);
}
public function testCheckedRadio()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', true);
$this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')),
'/div
[@class="form-check"]
[
./label
[@class="form-check-label required"]
[
./input
[@id="my&id"]
[@type="radio"]
[@name="name"]
[@class="my&class"]
[@checked="checked"]
[@value="1"]
]
]
'
);
}
public function testUncheckedRadio()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false);
$this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')),
'/div
[@class="form-check"]
[
./label
[@class="form-check-label required"]
[
./input
[@id="my&id"]
[@type="radio"]
[@name="name"]
[@class="my&class"]
[not(@checked)]
]
]
'
);
}
public function testRadioWithValue()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false, array(
'value' => 'foo&bar',
));
$this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')),
'/div
[@class="form-check"]
[
./label
[@class="form-check-label required"]
[
./input
[@id="my&id"]
[@type="radio"]
[@name="name"]
[@class="my&class"]
[@value="foo&bar"]
]
]
'
);
}
public function testButtonAttributeNameRepeatedIfTrue()
{
$form = $this->factory->createNamed('button', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', null, array(
'attr' => array('foo' => true),
));
$html = $this->renderWidget($form->createView());
// foo="foo"
$this->assertSame('<button type="button" id="button" name="button" foo="foo" class="btn-secondary btn">[trans]Button[/trans]</button>', $html);
}
public function testFile()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\FileType');
$this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'my&class form-control-file')),
'/input
[@type="file"]
'
);
}
}