Settings Modernization: Applied suggested changed from #972

This commit is contained in:
frischler 2022-10-24 14:59:34 +02:00 committed by Igor Scheller
parent dba7bc29f9
commit 35815b0838
7 changed files with 122 additions and 84 deletions

View File

@ -47,10 +47,6 @@
</span>
{%- endmacro %}
{% macro entry_required(text) %}
<h4 class="help-block">{{ _self.icon('exclamation-triangle', 'info') }}{{ text }}</h4>
{%- endmacro %}
{% macro type_bg_class() -%}
{% if theme.type == 'light' %}bg-white{% else %}bg-dark{% endif %}
{%- endmacro %}

View File

@ -1,10 +1,14 @@
{% macro entry_required() %}
<span class="bi bi-exclamation-triangle text-info"></span>
{%- endmacro %}
{% macro input(name, label, type, opt) %}
<div class="mb-3">
{% if label -%}
<label for="{{ name }}" class="form-label {% if opt.hide_label|default(false) %}sr-only{% endif %}">
{{ label }}
{% if opt.entry_required_icon|default(false) %}
<span class="bi bi-exclamation-triangle text-info"></span>
{{ _self.entry_required() }}
{% endif %}
</label>
{%- endif %}
@ -48,7 +52,12 @@
{% macro select(name, data, label, selected, opt) %}
<div class="mb-3">
{% if label -%}
<label class="form-label" for="{{ name }}">{{ label }}</label>
<label class="form-label" for="{{ name }}">
{{ label }}
{% if opt.entry_required_icon|default(false) %}
{{ _self.entry_required() }}
{% endif %}
</label>
{% endif %}
<select id="{{ name }}" name="{{ name }}"
class="form-control {%- if opt.class is defined %} {{ opt.class }}{% endif %}"

View File

@ -2,16 +2,21 @@
{% import 'macros/form.twig' as f %}
{% import 'macros/base.twig' as m %}
{% block title %}{{ __('settings.language') }}{% endblock %}
{% block title %}{{ __('settings.profile') }}{% endblock %}
{% block row_content %}
<form action="" enctype="multipart/form-data" method="post">
{{ csrf() }}
<div class="row g-4">
<div class="col-md-12">
<strong class="help-block">
{{ f.entry_required() }} = {{ __('settings.profile.entry_required') }}
</strong>
</div>
<div class="col-md-12">
{{ m.info(__('settings.profile.user_details.info')) }}
{{ m.entry_required(' = ' ~ __('settings.profile.entry_required')) }}
{{ f.input(
'nick',
__('settings.profile.nick'),
@ -133,17 +138,23 @@
{% endif %}
</div>
<div class="col-md-12">
{{ f.select(
'shirt_size',
config('tshirt_sizes'),
__('settings.profile.shirt_size'),
user.personalData.shirt_size
) }}
</div>
{% if config('enable_tshirt_size') %}
<div class="col-md-12">
{{ f.select(
'shirt_size',
config('tshirt_sizes'),
__('settings.profile.shirt_size'),
user.personalData.shirt_size,
{'required': true, 'entry_required_icon': true}
) }}
</div>
{% endif %}
<div class="col-md-12">
{{ m.info(__('settings.profile.angeltypes.info', [url('/angeltypes')]), true) }}
</div>
<div class="col-md-12">
{{ f.submit() }}
</div>
</div>

View File

@ -7,6 +7,11 @@ use DateTime;
trait ChecksArrivalsAndDepartures
{
/**
* @param string|null $arrival
* @param string|null $departure
* @return bool
*/
protected function isArrivalDateValid(?string $arrival, ?string $departure): bool
{
$arrival_carbon = $this->toCarbon($arrival);
@ -23,6 +28,11 @@ trait ChecksArrivalsAndDepartures
return !$this->isBeforeBuildup($arrival_carbon) && !$this->isAfterTeardown($arrival_carbon);
}
/**
* @param string|null $arrival
* @param string|null $departure
* @return bool
*/
protected function isDepartureDateValid(?string $arrival, ?string $departure): bool
{
$arrival_carbon = $this->toCarbon($arrival);
@ -45,12 +55,12 @@ trait ChecksArrivalsAndDepartures
private function isBeforeBuildup(Carbon $date): bool
{
$buildup = config('buildup_start');
return !empty($buildup) && $date->lessThan($buildup->setTime(0, 0));
return !empty($buildup) && $date->lessThan($buildup->startOfDay());
}
private function isAfterTeardown(Carbon $date): bool
{
$teardown = config('teardown_end');
return !empty($teardown) && $date->greaterThanOrEqualTo($teardown->addDay()->setTime(0, 0));
return !empty($teardown) && $date->greaterThanOrEqualTo($teardown->endOfDay());
}
}

View File

@ -36,7 +36,10 @@ class SettingsController extends BaseController
];
/**
* @param Config $config
* @param Authenticator $auth
* @param Config $config
* @param LoggerInterface $log
* @param Redirector $redirector
* @param Response $response
*/
public function __construct(
@ -53,6 +56,9 @@ class SettingsController extends BaseController
$this->response = $response;
}
/**
* @return Response
*/
public function profile(): Response
{
$user = $this->auth->user();
@ -73,26 +79,7 @@ class SettingsController extends BaseController
public function saveProfile(Request $request): Response
{
$user = $this->auth->user();
config('buildup_start');
config('teardown_end');
$data = $this->validate($request, [
'pronoun' => 'optional|max:15',
'first_name' => 'optional|max:64',
'last_name' => 'optional|max:64',
'planned_arrival_date' => 'required|date:Y-m-d',
'planned_departure_date' => 'optional|date:Y-m-d',
'dect' => 'optional|length:0:40', // dect/mobile can be purely numbers. "max" would have
'mobile' => 'optional|length:0:40', // checked their values, not their character length.
'mobile_show' => 'optional|checked',
'email' => 'required|email|max:254',
'email_shiftinfo' => 'optional|checked',
'email_news' => 'optional|checked',
'email_human' => 'optional|checked',
'email_goody' => 'optional|checked',
'shirt_size' => 'required',
]);
$data = $this->validate($request, $this->getSaveProfileRules());
if (config('enable_pronoun')) {
$user->personalData->pronoun = $data['pronoun'];
@ -135,9 +122,8 @@ class SettingsController extends BaseController
$user->settings->email_goody = $data['email_goody'] ?: false;
}
if (isset(config('tshirt_sizes')[$data['shirt_size']])) {
if (config('enable_tshirt_size') && isset(config('tshirt_sizes')[$data['shirt_size']])) {
$user->personalData->shirt_size = $data['shirt_size'];
$user->personalData->save();
}
$user->personalData->save();
@ -330,4 +316,32 @@ class SettingsController extends BaseController
return true;
}
/**
* @return string[]
*/
private function getSaveProfileRules(): array
{
$rules = [
'pronoun' => 'optional|max:15',
'first_name' => 'optional|max:64',
'last_name' => 'optional|max:64',
'dect' => 'optional|length:0:40', // dect/mobile can be purely numbers. "max" would have
'mobile' => 'optional|length:0:40', // checked their values, not their character length.
'mobile_show' => 'optional|checked',
'email' => 'required|email|max:254',
'email_shiftinfo' => 'optional|checked',
'email_news' => 'optional|checked',
'email_human' => 'optional|checked',
'email_goody' => 'optional|checked',
];
if (config('enable_planned_arrival')) {
$rules['planned_arrival_date'] = 'required|date:Y-m-d';
$rules['planned_departure_date'] = 'optional|date:Y-m-d';
}
if (config('enable_tshirt_size')) {
$rules['shirt_size'] = 'required';
}
return $rules;
}
}

View File

@ -37,12 +37,12 @@ abstract class ControllerTest extends TestCase
/**
* @param string $value
* @param string|null $message
* @param string|null $type
*/
protected function assertHasNotification(string $value, string $message = null)
protected function assertHasNotification(string $value, string $type = 'messages')
{
$messages = $this->session->get('messages', []);
$this->assertTrue(in_array($value, $messages), $message ?: 'Session does not contain message "' . $value . '"');
$messages = $this->session->get($type, []);
$this->assertTrue(in_array($value, $messages));
}
/**

View File

@ -2,52 +2,31 @@
namespace Engelsystem\Test\Unit\Controllers;
use Carbon\Carbon;
use Engelsystem\Config\Config;
use Engelsystem\Controllers\SettingsController;
use Engelsystem\Http\Exceptions\HttpNotFound;
use Engelsystem\Http\Response;
use Engelsystem\Models\User\Settings;
use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Psr\Log\LoggerInterface;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Http\Request;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\Test\TestLogger;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Http\UrlGenerator;
use Engelsystem\Models\User\User;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Http\Exceptions\ValidationException;
class SettingsControllerTest extends TestCase
class SettingsControllerTest extends ControllerTest
{
use HasDatabase;
/** @var Authenticator|MockObject */
protected $auth;
/** @var Config */
protected $config;
/** @var TestLogger */
protected $log;
/** @var Response|MockObject */
protected $response;
/** @var Request */
protected $request;
/** @var User */
protected $user;
/** @var Session */
protected $session;
/** @var SettingsController */
protected $controller;
@ -85,6 +64,7 @@ class SettingsControllerTest extends TestCase
'enable_dect' => true,
'enable_mobile_show' => true,
'enable_goody' => true,
'enable_tshirt_size' => true,
]);
$this->setExpects($this->auth, 'user', null, $this->user, $this->atLeastOnce());
@ -117,6 +97,7 @@ class SettingsControllerTest extends TestCase
/**
* @covers \Engelsystem\Controllers\SettingsController::saveProfile
* @covers \Engelsystem\Controllers\SettingsController::getSaveProfileRules
*/
public function testSaveProfile()
{
@ -145,6 +126,28 @@ class SettingsControllerTest extends TestCase
$this->assertEquals($body['shirt_size'], $this->user->personalData->shirt_size);
}
/**
* @covers \Engelsystem\Controllers\SettingsController::saveProfile
*/
public function testSaveProfileThrowsErrorOnInvalidArrival()
{
$this->setUpProfileTest();
config(['buildup_start' => new Carbon('2022-01-02')]); // arrival before buildup
$this->controller->saveProfile($this->request);
$this->assertHasNotification('settings.profile.planned_arrival_date.invalid', 'errors');
}
/**
* @covers \Engelsystem\Controllers\SettingsController::saveProfile
*/
public function testSaveProfileThrowsErrorOnInvalidDeparture()
{
$this->setUpProfileTest();
config(['teardown_end' => new Carbon('2022-01-01')]); // departure after teardown
$this->controller->saveProfile($this->request);
$this->assertHasNotification('settings.profile.planned_departure_date.invalid', 'errors');
}
/**
* @covers \Engelsystem\Controllers\SettingsController::saveProfile
*/
@ -213,6 +216,17 @@ class SettingsControllerTest extends TestCase
$this->assertFalse($this->user->settings->email_goody);
}
/**
* @covers \Engelsystem\Controllers\SettingsController::saveProfile
*/
public function testSaveProfileIgnoresTShirtSizeIfDisabled()
{
$this->setUpProfileTest();
config(['enable_tshirt_size' => false]);
$this->controller->saveProfile($this->request);
$this->assertEquals('', $this->user->personalData->shirt_size);
}
/**
* @covers \Engelsystem\Controllers\SettingsController::password
*/
@ -619,7 +633,6 @@ class SettingsControllerTest extends TestCase
public function setUp(): void
{
parent::setUp();
$this->initDatabase();
$themes = [
0 => ['name' => 'Engelsystem light'],
@ -639,23 +652,8 @@ class SettingsControllerTest extends TestCase
$this->app->instance('config', $this->config);
$this->app->instance(Config::class, $this->config);
$this->request = Request::create('http://localhost');
$this->app->instance('request', $this->request);
$this->app->instance(Request::class, $this->request);
$this->app->instance(ServerRequestInterface::class, $this->request);
$this->response = $this->createMock(Response::class);
$this->app->instance(Response::class, $this->response);
$this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
$this->app->bind('http.urlGenerator', UrlGenerator::class);
$this->log = new TestLogger();
$this->app->instance(LoggerInterface::class, $this->log);
$this->session = new Session(new MockArraySessionStorage());
$this->app->instance('session', $this->session);
$this->auth = $this->createMock(Authenticator::class);
$this->app->instance(Authenticator::class, $this->auth);