From 203531629f290057032da60ba3bdebff53b4ead3 Mon Sep 17 00:00:00 2001 From: frischler Date: Mon, 3 Oct 2022 18:58:04 +0200 Subject: [PATCH] Settings Modernization: Creating /settings/profile page with Controller methods and tests --- config/routes.php | 2 + resources/views/macros/base.twig | 4 + resources/views/macros/form.twig | 8 +- resources/views/pages/settings/profile.twig | 130 ++++++++++ src/Controllers/SettingsController.php | 77 +++++- .../Controllers/SettingsControllerTest.php | 242 +++++++++++++----- tests/Unit/Http/Validation/ValidatorTest.php | 21 ++ 7 files changed, 412 insertions(+), 72 deletions(-) create mode 100644 resources/views/pages/settings/profile.twig diff --git a/config/routes.php b/config/routes.php index a0a843a1..e88f9581 100644 --- a/config/routes.php +++ b/config/routes.php @@ -20,6 +20,8 @@ $route->post('/oauth/{provider}/connect', 'OAuthController@connect'); $route->post('/oauth/{provider}/disconnect', 'OAuthController@disconnect'); // User settings +$route->get('/settings/profile', 'SettingsController@profile'); +$route->post('/settings/profile', 'SettingsController@saveProfile'); $route->get('/settings/password', 'SettingsController@password'); $route->post('/settings/password', 'SettingsController@savePassword'); $route->get('/settings/theme', 'SettingsController@theme'); diff --git a/resources/views/macros/base.twig b/resources/views/macros/base.twig index b6dfb19e..dd8feff6 100644 --- a/resources/views/macros/base.twig +++ b/resources/views/macros/base.twig @@ -40,6 +40,10 @@ {{ _self.icon('info-circle') }}{{ text }} {%- endmacro %} +{% macro entry_required(text) %} +

{{ _self.icon('exclamation-triangle', 'info') }}{{ text }}

+{%- endmacro %} + {% macro type_bg_class() -%} {% if theme.type == 'light' %}bg-white{% else %}bg-dark{% endif %} {%- endmacro %} diff --git a/resources/views/macros/form.twig b/resources/views/macros/form.twig index 23f2b7e7..346ebea3 100644 --- a/resources/views/macros/form.twig +++ b/resources/views/macros/form.twig @@ -1,13 +1,19 @@ {% macro input(name, label, type, opt) %}
{% if label -%} - + {%- endif %} + {{ csrf() }} + +
+
+ {{ m.info(__('settings.profile.user_details.info')) }} + {{ m.entry_required(' = ' ~ __('settings.profile.entry_required')) }} + {{ f.input( + 'nick', + __('settings.profile.nick'), + 'text', + {'value': user.name, 'disabled': true} + ) }} + {% if config('enable_pronoun') %} + {{ f.input( + 'pronoun', + __('settings.profile.pronoun'), + 'text', + {'value': user.personalData.pronoun ,'max': 15} + ) }} + {{ m.info(__('settings.profile.pronoun.info')) }} + {% endif %} + {% if config('enable_user_name') %} + {{ f.input( + 'first_name', + __('settings.profile.firstname'), + 'text', + {'value': user.personalData.first_name, 'max': 64} + ) }} + {{ f.input( + 'last_name', + __('settings.profile.lastname'), + 'text', + {'value': user.personalData.last_name, 'max': 64} + ) }} + {% endif %} +
+ + {% if config('enable_planned_arrival') %} +
+ {{ f.input( + 'planned_arrival_date', + __('settings.profile.planned_arrival_date'), + 'date', + { + 'value': user.personalData.planned_arrival_date.format(__('Y-m-d')), + 'required': true, + 'entry_required_icon': true + } + ) }} + {{ f.input( + 'planned_departure_date', + __('settings.profile.planned_departure_date'), + 'text', + {'value': user.personalData.planned_departure_date.format(__('Y-m-d'))} + ) }} +
+ {% endif %} + +
+ {% if config('enable_dect') %} + {{ f.input( + 'dect', + __('settings.profile.dect'), + 'text', + {'value': user.contact.dect, 'max': 40} + ) }} + {% endif %} + {{ f.input( + 'mobile', + __('settings.profile.mobile'), + 'text', + {'value': user.contact.mobile, 'max': 40} + ) }} + {{ f.input( + 'email', + __('settings.profile.email'), + 'email', + {'value': user.email, 'max': 254, 'required': true, 'entry_required_icon': true} + ) }} +
+ +
+ {{ f.checkbox( + 'email_shiftinfo', + __('settings.profile.email_shiftinfo'), + user.settings.email_shiftinfo + ) }} + {{ f.checkbox( + 'email_news', + __('settings.profile.email_news'), + user.settings.email_news + ) }} + {{ f.checkbox( + 'email_human', + __('settings.profile.email_by_human_allowed'), + user.settings.email_human + ) }} + {% if config('enable_goody') %} + {{ f.checkbox( + 'email_goody', + __('settings.profile.email_goody'), + user.settings.email_goody + ) }} + {% endif %} +
+ +
+ {{ f.select( + 'shirt_size', + config('tshirt_sizes'), + __('settings.profile.shirt_size'), + user.personalData.shirt_size + ) }} +
+ +
+ {{ m.info(__('settings.profile.user_details.info')) }} + {{ f.submit() }} +
+
+ +{% endblock %} diff --git a/src/Controllers/SettingsController.php b/src/Controllers/SettingsController.php index a3359126..59bcee46 100644 --- a/src/Controllers/SettingsController.php +++ b/src/Controllers/SettingsController.php @@ -52,6 +52,82 @@ class SettingsController extends BaseController $this->response = $response; } + public function profile(): Response + { + $user = $this->auth->user(); + + return $this->response->withView( + 'pages/settings/profile', + [ + 'settings_menu' => $this->settingsMenu(), + 'user' => $user, + ] + $this->getNotifications() + ); + } + + /** + * @param Request $request + * @return Response + */ + public function saveProfile(Request $request): Response + { + $user = $this->auth->user(); + $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. + 'email' => 'required|email|max:254', + 'email_shiftinfo' => 'optional|checked', + 'email_news' => 'optional|checked', + 'email_human' => 'optional|checked', + 'email_goody' => 'optional|checked', + 'shirt_size' => 'required', + ]); + + if (config('enable_pronoun')) { + $user->personalData->pronoun = $data['pronoun']; + } + + if (config('enable_user_name')) { + $user->personalData->first_name = $data['first_name']; + $user->personalData->last_name = $data['last_name']; + } + + if (config('enable_planned_arrival')) { + $user->personalData->planned_arrival_date = $data['planned_arrival_date']; + $user->personalData->planned_departure_date = $data['planned_departure_date']; + } + + if (config('enable_dect')) { + $user->contact->dect = $data['dect']; + } + + $user->contact->mobile = $data['mobile']; + $user->email = $data['email']; + $user->settings->email_shiftinfo = $data['email_shiftinfo']; + $user->settings->email_news = $data['email_news']; + $user->settings->email_human = $data['email_human']; + + if (config('enable_goody')) { + $user->settings->email_goody = $data['email_goody']; + } + + $user->personalData->shirt_size = $data['shirt_size']; + + $user->personalData->save(); + $user->contact->save(); + $user->settings->save(); + $user->save(); + + $this->addNotification('settings.profile.success'); + + return $this->redirect->to('/settings/profile'); + } + /** * @return Response */ @@ -62,7 +138,6 @@ class SettingsController extends BaseController [ 'settings_menu' => $this->settingsMenu(), 'min_length' => config('min_password_length') - ] + $this->getNotifications() ); } diff --git a/tests/Unit/Controllers/SettingsControllerTest.php b/tests/Unit/Controllers/SettingsControllerTest.php index fddf8936..0563e3b9 100644 --- a/tests/Unit/Controllers/SettingsControllerTest.php +++ b/tests/Unit/Controllers/SettingsControllerTest.php @@ -3,10 +3,15 @@ namespace Engelsystem\Test\Unit\Controllers; use Engelsystem\Config\Config; +use Engelsystem\Controllers\Admin\UserShirtController; use Engelsystem\Controllers\SettingsController; use Engelsystem\Http\Exceptions\HttpNotFound; +use Engelsystem\Http\Redirector; use Engelsystem\Http\Response; +use Engelsystem\Models\User\Contact; +use Engelsystem\Models\User\PersonalData; use Engelsystem\Models\User\Settings; +use Engelsystem\Models\User\State; use Engelsystem\Test\Unit\TestCase; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\HttpFoundation\Session\Session; @@ -48,6 +53,150 @@ class SettingsControllerTest extends TestCase /** @var Session */ protected $session; + /** @var SettingsController */ + protected $controller; + + protected function setUpProfileTest() { + $body = [ + 'pronoun' => 'Herr', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'planned_arrival_date' => '2022-01-01', + 'planned_departure_date' => '2022-01-02', + 'dect' => '1234', + 'mobile' => '0123456789', + 'email' => 'a@bc.de', + 'email_shiftinfo' => true, + 'email_news' => true, + 'email_human' => true, + 'email_goody' => true, + 'shirt_size' => 'S', + ]; + $this->request = $this->request->withParsedBody($body); + $this->setExpects( + $this->response, + 'redirectTo', + ['http://localhost/settings/profile'], + $this->response, + $this->atLeastOnce() + ); + + config([ + 'enable_pronoun' => true, + 'enable_user_name' => true, + 'enable_planned_arrival' => true, + 'enable_dect' => true, + 'enable_goody' => true, + ]); + + $this->setExpects($this->auth, 'user', null, $this->user, $this->atLeastOnce()); + + $this->controller = $this->app->make(SettingsController::class); + $this->controller->setValidator(new Validator()); + + return $body; + } + + /** + * @covers \Engelsystem\Controllers\SettingsController::profile + */ + public function testProfile() + { + $this->setExpects($this->auth, 'user', null, $this->user, $this->once()); + /** @var Response|MockObject $response */ + $this->response->expects($this->once()) + ->method('withView') + ->willReturnCallback(function ($view, $data) { + $this->assertEquals('pages/settings/profile', $view); + $this->assertArrayHasKey('user', $data); + $this->assertEquals($this->user, $data['user']); + return $this->response; + }); + + $this->controller = $this->app->make(SettingsController::class); + $this->controller->profile(); + } + + /** + * @covers \Engelsystem\Controllers\SettingsController::saveProfile + */ + public function testSaveProfile() + { + $body = $this->setUpProfileTest(); + $this->controller->saveProfile($this->request); + + $this->assertEquals($body['pronoun'], $this->user->personalData->pronoun); + $this->assertEquals($body['first_name'], $this->user->personalData->first_name); + $this->assertEquals($body['last_name'], $this->user->personalData->last_name); + $this->assertEquals($body['planned_arrival_date'], $this->user->personalData->planned_arrival_date->format('Y-m-d')); + $this->assertEquals($body['planned_departure_date'], $this->user->personalData->planned_departure_date->format('Y-m-d')); + $this->assertEquals($body['dect'], $this->user->contact->dect); + $this->assertEquals($body['mobile'], $this->user->contact->mobile); + $this->assertEquals($body['email'], $this->user->email); + $this->assertEquals($body['email_shiftinfo'], $this->user->settings->email_shiftinfo); + $this->assertEquals($body['email_news'], $this->user->settings->email_news); + $this->assertEquals($body['email_human'], $this->user->settings->email_human); + $this->assertEquals($body['email_goody'], $this->user->settings->email_goody); + $this->assertEquals($body['shirt_size'], $this->user->personalData->shirt_size); + } + + /** + * @covers \Engelsystem\Controllers\SettingsController::saveProfile + */ + public function testSaveProfileIgnoresPronounIfDisabled() + { + $this->setUpProfileTest(); + config(['enable_pronoun' => false]); + $this->controller->saveProfile($this->request); + $this->assertEquals('', $this->user->personalData->pronoun); + } + + /** + * @covers \Engelsystem\Controllers\SettingsController::saveProfile + */ + public function testSaveProfileIgnoresFirstAndLastnameIfDisabled() + { + $this->setUpProfileTest(); + config(['enable_user_name' => false]); + $this->controller->saveProfile($this->request); + $this->assertEquals('', $this->user->personalData->first_name); + $this->assertEquals('', $this->user->personalData->last_name); + } + + /** + * @covers \Engelsystem\Controllers\SettingsController::saveProfile + */ + public function testSaveProfileIgnoresArrivalDatesIfDisabled() + { + $this->setUpProfileTest(); + config(['enable_planned_arrival' => false]); + $this->controller->saveProfile($this->request); + $this->assertEquals('', $this->user->personalData->planned_arrival_date); + $this->assertEquals('', $this->user->personalData->planned_departure_date); + } + + /** + * @covers \Engelsystem\Controllers\SettingsController::saveProfile + */ + public function testSaveProfileIgnoresDectIfDisabled() + { + $this->setUpProfileTest(); + config(['enable_dect' => false]); + $this->controller->saveProfile($this->request); + $this->assertEquals('', $this->user->contact->dect); + } + + /** + * @covers \Engelsystem\Controllers\SettingsController::saveProfile + */ + public function testSaveProfileIgnoresEmailGoodyIfDisabled() + { + $this->setUpProfileTest(); + config(['enable_goody' => false]); + $this->controller->saveProfile($this->request); + $this->assertFalse($this->user->settings->email_goody); + } + /** * @covers \Engelsystem\Controllers\SettingsController::password */ @@ -62,9 +211,7 @@ class SettingsControllerTest extends TestCase return $this->response; }); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->password(); + $this->controller->password(); } /** @@ -90,10 +237,7 @@ class SettingsControllerTest extends TestCase $this->once() ); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->savePassword($this->request); + $this->controller->savePassword($this->request); $this->assertTrue($this->log->hasInfoThatContains('User set new password.')); @@ -127,10 +271,7 @@ class SettingsControllerTest extends TestCase $this->once() ); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->savePassword($this->request); + $this->controller->savePassword($this->request); } /** @@ -156,10 +297,7 @@ class SettingsControllerTest extends TestCase $this->once() ); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->savePassword($this->request); + $this->controller->savePassword($this->request); /** @var Session $session */ $session = $this->app->get('session'); @@ -190,10 +328,7 @@ class SettingsControllerTest extends TestCase $this->once() ); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->savePassword($this->request); + $this->controller->savePassword($this->request); /** @var Session $session */ $session = $this->app->get('session'); @@ -238,10 +373,7 @@ class SettingsControllerTest extends TestCase $this->expectException(ValidationException::class); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->savePassword($this->request); + $this->controller->savePassword($this->request); } /** @@ -266,9 +398,7 @@ class SettingsControllerTest extends TestCase return $this->response; }); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->theme(); + $this->controller->theme(); } /** @@ -280,10 +410,7 @@ class SettingsControllerTest extends TestCase $this->setExpects($this->auth, 'user', null, $this->user, $this->once()); $this->expectException(ValidationException::class); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->saveTheme($this->request); + $this->controller->saveTheme($this->request); } /** @@ -297,10 +424,7 @@ class SettingsControllerTest extends TestCase $this->setExpects($this->auth, 'user', null, $this->user, $this->once()); $this->expectException(HttpNotFound::class); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->saveTheme($this->request); + $this->controller->saveTheme($this->request); } /** @@ -318,10 +442,7 @@ class SettingsControllerTest extends TestCase $this->request = $this->request->withParsedBody(['select_theme' => 0]); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->saveTheme($this->request); + $this->controller->saveTheme($this->request); $this->assertEquals(0, $this->user->settings->theme); } @@ -348,9 +469,7 @@ class SettingsControllerTest extends TestCase return $this->response; }); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->language(); + $this->controller->language(); } /** @@ -362,10 +481,7 @@ class SettingsControllerTest extends TestCase $this->setExpects($this->auth, 'user', null, $this->user, $this->once()); $this->expectException(ValidationException::class); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->saveLanguage($this->request); + $this->controller->saveLanguage($this->request); } /** @@ -379,10 +495,7 @@ class SettingsControllerTest extends TestCase $this->setExpects($this->auth, 'user', null, $this->user, $this->once()); $this->expectException(HttpNotFound::class); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->saveLanguage($this->request); + $this->controller->saveLanguage($this->request); } /** @@ -402,10 +515,7 @@ class SettingsControllerTest extends TestCase $this->request = $this->request->withParsedBody(['select_language' => 'de_DE']); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->setValidator(new Validator()); - $controller->saveLanguage($this->request); + $this->controller->saveLanguage($this->request); $this->assertEquals('de_DE', $this->user->settings->language); $this->assertEquals('de_DE', $this->session->get('locale')); @@ -430,9 +540,7 @@ class SettingsControllerTest extends TestCase return $this->response; }); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $controller->oauth(); + $this->controller->oauth(); } /** @@ -442,11 +550,8 @@ class SettingsControllerTest extends TestCase { config(['oauth' => []]); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $this->expectException(HttpNotFound::class); - $controller->oauth(); + $this->controller->oauth(); } /** @@ -459,16 +564,13 @@ class SettingsControllerTest extends TestCase $providersHidden = ['foo' => ['lorem' => 'ipsum', 'hidden' => true]]; config(['oauth' => $providers]); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $this->assertEquals([ 'http://localhost/user-settings' => 'settings.profile', 'http://localhost/settings/password' => 'settings.password', 'http://localhost/settings/language' => 'settings.language', 'http://localhost/settings/theme' => 'settings.theme', 'http://localhost/settings/oauth' => ['title' => 'settings.oauth', 'hidden' => false] - ], $controller->settingsMenu()); + ], $this->controller->settingsMenu()); config(['oauth' => $providersHidden]); $this->assertEquals([ @@ -477,7 +579,7 @@ class SettingsControllerTest extends TestCase 'http://localhost/settings/language' => 'settings.language', 'http://localhost/settings/theme' => 'settings.theme', 'http://localhost/settings/oauth' => ['title' => 'settings.oauth', 'hidden' => true] - ], $controller->settingsMenu()); + ], $this->controller->settingsMenu()); } /** @@ -487,15 +589,12 @@ class SettingsControllerTest extends TestCase { config(['oauth' => []]); - /** @var SettingsController $controller */ - $controller = $this->app->make(SettingsController::class); - $this->assertEquals([ 'http://localhost/user-settings' => 'settings.profile', 'http://localhost/settings/password' => 'settings.password', 'http://localhost/settings/language' => 'settings.language', 'http://localhost/settings/theme' => 'settings.theme' - ], $controller->settingsMenu()); + ], $this->controller->settingsMenu()); } /** @@ -539,7 +638,10 @@ class SettingsControllerTest extends TestCase $this->app->instance(Authenticator::class, $this->auth); $this->user = User::factory() - ->has(Settings::factory(['theme' => 1, 'language' => 'en_US'])) + ->has(Settings::factory(['theme' => 1, 'language' => 'en_US', 'email_goody' => false])) ->create(); + + $this->controller = $this->app->make(SettingsController::class); + $this->controller->setValidator(new Validator()); } } diff --git a/tests/Unit/Http/Validation/ValidatorTest.php b/tests/Unit/Http/Validation/ValidatorTest.php index 124673df..35a9716d 100644 --- a/tests/Unit/Http/Validation/ValidatorTest.php +++ b/tests/Unit/Http/Validation/ValidatorTest.php @@ -60,6 +60,27 @@ class ValidatorTest extends TestCase )); } + /** + * @covers \Engelsystem\Http\Validation\Validator::validate + */ + public function testValidateMultipleParameters() + { + $val = new Validator(); + + $this->assertFalse($val->validate( + ['lorem' => 'h'], + ['lorem' => 'length:2:3'] + )); + $this->assertTrue($val->validate( + ['lorem' => 'hey'], + ['lorem' => 'length:2:3'] + )); + $this->assertFalse($val->validate( + ['lorem' => 'heyy'], + ['lorem' => 'length:2:3'] + )); + } + /** * @covers \Engelsystem\Http\Validation\Validator::validate */