diff --git a/plugins/PollPlugin/Controller/RespondPoll.php b/plugins/Poll/Controller/AnswerPoll.php similarity index 84% rename from plugins/PollPlugin/Controller/RespondPoll.php rename to plugins/Poll/Controller/AnswerPoll.php index da38c04f1a..2544c0e5c2 100644 --- a/plugins/PollPlugin/Controller/RespondPoll.php +++ b/plugins/Poll/Controller/AnswerPoll.php @@ -19,7 +19,7 @@ // }}} -namespace Plugin\PollPlugin\Controller; +namespace Plugin\Poll\Controller; use App\Core\DB\DB; use App\Entity\Poll; @@ -29,10 +29,20 @@ use App\Util\Exception\InvalidFormException; use App\Util\Exception\NotFoundException; use App\Util\Exception\RedirectException; use App\Util\Exception\ServerException; -use Plugin\PollPlugin\Forms\PollResponseForm; +use Plugin\Poll\Forms\PollResponseForm; use Symfony\Component\HttpFoundation\Request; -class RespondPoll +/** + * Respond to a Poll + * + * @package GNUsocial + * @category PollPlugin + * + * @author Daniel Brandao + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class AnswerPoll { /** * Handle poll response @@ -48,40 +58,33 @@ class RespondPoll * * @return array template */ - public function respondpoll(Request $request, string $id) + public function answerPoll(Request $request, string $id) { $user = Common::ensureLoggedIn(); $poll = Poll::getFromId((int) $id); - //var_dump($poll); - if ($poll == null) { throw new NotFoundException('Poll does not exist'); } + $question = $poll->getQuestion(); - // echo $question; - $opts = $poll->getOptionsArr(); - //var_dump($opts); - - $form = PollResponseForm::make($opts); - + $opts = $poll->getOptionsArr(); + $form = PollResponseForm::make($opts); $form->handleRequest($request); if ($form->isSubmitted()) { if ($form->isValid()) { $data = $form->getData(); $selection = array_values($data)[1]; - //echo $selection; if (!$poll->isValidSelection($selection)) { throw new InvalidFormException(); } if (PollResponse::exits($poll->getId(), $user->getId())) { throw new ServerException('User already responded to poll'); } - $pollResponse = PollResponse::create(['poll_id' => $poll->getId(), 'gsactor_id' => $user->getId(), 'selection' => $selection]); DB::persist($pollResponse); DB::flush(); - //var_dump($pollResponse); + throw new RedirectException('showpoll', ['id' => $poll->getId()]); } else { throw new InvalidFormException(); diff --git a/plugins/PollPlugin/Controller/NewPoll.php b/plugins/Poll/Controller/NewPoll.php similarity index 62% rename from plugins/PollPlugin/Controller/NewPoll.php rename to plugins/Poll/Controller/NewPoll.php index a210d2c5d6..620b8ec2e7 100644 --- a/plugins/PollPlugin/Controller/NewPoll.php +++ b/plugins/Poll/Controller/NewPoll.php @@ -19,14 +19,15 @@ // }}} -namespace Plugin\PollPlugin\Controller; +namespace Plugin\Poll\Controller; use App\Core\DB\DB; +use App\Core\Security; use App\Entity\Poll; use App\Util\Common; use App\Util\Exception\InvalidFormException; use App\Util\Exception\RedirectException; -use Plugin\PollPlugin\Forms\NewPollForm; +use Plugin\Poll\Forms\NewPollForm; use Symfony\Component\HttpFoundation\Request; const MAX_OPTS = 5; @@ -46,55 +47,31 @@ class NewPoll * * @return array template */ - public function newpoll(Request $request, int $num) + public function newPoll(Request $request, int $num) { $user = Common::ensureLoggedIn(); - $numOptions = min(max($num,MIN_OPTS),MAX_OPTS); + $numOptions = Common::clamp($num,MIN_OPTS,MAX_OPTS); $form = NewPollForm::make($numOptions); $form->handleRequest($request); $opt = []; if ($form->isSubmitted()) { if ($form->isValid()) { $data = $form->getData(); - //var_dump($data); - $question = $data['Question']; + Security::sanitize($question = $data['Question']); for ($i = 1; $i <= $numOptions; ++$i) { - array_push($opt, $data['Option_' . $i]); + Security::sanitize($opt[$i - 1] = $data['Option_' . $i]); } - $poll = Poll::make($user->getId(), $question, $opt); + + $options = implode("\n",$opt); + $poll = Poll::create(['gsactor_id' => $user->getId(), 'question' => $question, 'options' => $options]); DB::persist($poll); DB::flush(); - //var_dump($testPoll); throw new RedirectException('showpoll', ['id' => $poll->getId()]); } else { throw new InvalidFormException(); } } - // testing - - //$test = Poll::create(['id' => '0', 'uri' => 'a']); - //DB::persist($test); - //DB::flush(); - /* - $loadpoll = Poll::getFromId('0'); - var_dump($loadpoll); - */ - return ['_template' => 'Poll/newpoll.html.twig', 'form' => $form->createView()]; } - /* - public function pollsettings(Request $request) - { - $form = Form::create([['Num_of_Questions', NumberType::class, ['label' => _m(('Number of questions:'))]],['save', SubmitType::class, ['label' => _m('Continue')]]]); - $form->handleRequest($request); - if ($form->isSubmitted()) - { - $data = $form->getData(); - NewPoll::numOptions = $data['Num_of_Questions']; - var_dump($data); - } - return ['_template' => 'Poll/newpoll.html.twig', 'form' => $form->createView()]; - } - */ } diff --git a/plugins/PollPlugin/Controller/ShowPoll.php b/plugins/Poll/Controller/ShowPoll.php similarity index 92% rename from plugins/PollPlugin/Controller/ShowPoll.php rename to plugins/Poll/Controller/ShowPoll.php index 1ef691b33f..87b57814b3 100644 --- a/plugins/PollPlugin/Controller/ShowPoll.php +++ b/plugins/Poll/Controller/ShowPoll.php @@ -19,7 +19,7 @@ // }}} -namespace Plugin\PollPlugin\Controller; +namespace Plugin\Poll\Controller; use App\Entity\Poll; use App\Util\Common; @@ -39,12 +39,11 @@ class ShowPoll * * @return array Template */ - public function showpoll(Request $request, string $id) + public function showPoll(Request $request, string $id) { $user = Common::ensureLoggedIn(); $poll = Poll::getFromId((int) $id); - //var_dump($poll); if ($poll == null) { throw new NotFoundException('Poll does not exist'); diff --git a/plugins/PollPlugin/Forms/NewPollForm.php b/plugins/Poll/Forms/NewPollForm.php similarity index 66% rename from plugins/PollPlugin/Forms/NewPollForm.php rename to plugins/Poll/Forms/NewPollForm.php index 703f619a04..1b879c9aa0 100644 --- a/plugins/PollPlugin/Forms/NewPollForm.php +++ b/plugins/Poll/Forms/NewPollForm.php @@ -19,7 +19,7 @@ // }}} -namespace Plugin\PollPlugin\Forms; +namespace Plugin\Poll\Forms; use App\Core\Form; use function App\Core\I18n\_m; @@ -27,7 +27,16 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Form as SymfForm; -const MAX_OPT = 5; +/** + * Form to add a Poll + * + * @package GNUsocial + * @category PollPlugin + * + * @author Daniel Brandao + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ class NewPollForm extends Form { /** @@ -39,14 +48,14 @@ class NewPollForm extends Form */ public static function make(int $optionNum): SymfForm { - $optionNum = min(MAX_OPT,$optionNum); - $options = []; - array_push($options,['Question', TextType::class, ['label' => _m(('Question'))]]); - for ($i = 1; $i <= $optionNum; ++$i) { + $options = []; + $options[0] = ['Question', TextType::class, ['label' => _m(('Question'))]]; + $i = 1; + for ($i; $i <= $optionNum; ++$i) { //['Option_i', TextType::class, ['label' => _m('Option i')]], - array_push($options,['Option_' . $i, TextType::class, ['label' => _m(('Option ' . $i))]]); + $options[$i] = ['Option_' . $i, TextType::class, ['label' => _m(('Option ' . $i))]]; } - array_push($options, ['save', SubmitType::class, ['label' => _m('Submit Poll')]]); + $options[$i + 1] = ['save', SubmitType::class, ['label' => _m('Submit Poll')]]; return parent::create($options); } diff --git a/plugins/PollPlugin/Forms/PollResponseForm.php b/plugins/Poll/Forms/PollResponseForm.php similarity index 87% rename from plugins/PollPlugin/Forms/PollResponseForm.php rename to plugins/Poll/Forms/PollResponseForm.php index 65758870fc..abc9699217 100644 --- a/plugins/PollPlugin/Forms/PollResponseForm.php +++ b/plugins/Poll/Forms/PollResponseForm.php @@ -19,7 +19,7 @@ // }}} -namespace Plugin\PollPlugin\Forms; +namespace Plugin\Poll\Forms; use App\Core\Form; use function App\Core\I18n\_m; @@ -43,11 +43,11 @@ class PollResponseForm extends Form for ($i = 1; $i <= count($opts); ++$i) { $options[$opts[$i - 1]] = $i; } - array_push($formOptions, ['Options:', ChoiceType::class, [ + $formOptions[0] = ['Options:', ChoiceType::class, [ 'choices' => $options, 'expanded' => true, - ]]); - array_push($formOptions, ['save', SubmitType::class, ['label' => _m('Submit')]]); + ]]; + $formOptions[1] = ['save', SubmitType::class, ['label' => _m('Submit')]]; return parent::create($formOptions); } diff --git a/plugins/PollPlugin/PollPlugin.php b/plugins/Poll/Poll.php similarity index 52% rename from plugins/PollPlugin/PollPlugin.php rename to plugins/Poll/Poll.php index 57581a9268..d4b4147dbf 100644 --- a/plugins/PollPlugin/PollPlugin.php +++ b/plugins/Poll/Poll.php @@ -18,51 +18,27 @@ // }}} -namespace Plugin\PollPlugin; +namespace Plugin\Poll; use App\Core\Event; use App\Core\Module; use App\Core\Router\RouteLoader; -use Plugin\PollPlugin\Entity\Poll; use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; const ID_FMT = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'; -class PollPlugin extends Module +/** + * Poll plugin main class + * + * @package GNUsocial + * @category PollPlugin + * + * @author Daniel Brandao + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class Poll extends Module { - /** - * Map URLs to actions - * - * @param URLMapper $m path-to-action mapper - * @param mixed $r - * - * @return bool hook value; true means continue processing, false means stop. - */ - /* - public function onRouterInitialized(URLMapper $m) - { - $m->connect('main/poll/new', - ['action' => 'newpoll']); - - $m->connect('main/poll/:id', - ['action' => 'showpoll'], - ['id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}']); - - $m->connect('main/poll/response/:id', - ['action' => 'showpollresponse'], - ['id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}']); - - $m->connect('main/poll/:id/respond', - ['action' => 'respondpoll'], - ['id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}']); - - $m->connect('settings/poll', - ['action' => 'pollsettings']); - - return true; - } - */ - /** * Map URLs to actions * @@ -73,9 +49,8 @@ class PollPlugin extends Module public function onAddRoute(RouteLoader $r): bool { $r->connect('newpollnum', 'main/poll/new/{num<\\d*>}', [Controller\NewPoll::class, 'newpoll']); - //$r->connect('showpoll', 'main/poll/:{id<' . ID_FMT . '>}' , [Controller\ShowPoll::class, 'showpoll']); //doesnt work $r->connect('showpoll', 'main/poll/{id<\\d*>}',[Controller\ShowPoll::class, 'showpoll']); - $r->connect('respondpoll', 'main/poll/{id<\\d*>}/respond',[Controller\RespondPoll::class, 'respondpoll']); + $r->connect('answerpoll', 'main/poll/{id<\\d*>}/respond',[Controller\AnswerPoll::class, 'answerpoll']); $r->connect('newpoll', 'main/poll/new', RedirectController::class, ['defaults' => ['route' => 'newpollnum', 'num' => 3]]); return Event::next; diff --git a/src/Core/Entity.php b/src/Core/Entity.php index 295006bd72..7f3a75a7db 100644 --- a/src/Core/Entity.php +++ b/src/Core/Entity.php @@ -22,6 +22,7 @@ namespace App\Core; use App\Core\DB\DB; +use App\Util\Exception\NotFoundException; use App\Util\Formatting; use DateTime; @@ -76,4 +77,22 @@ abstract class Entity } DB::remove($obj); } + + /** + * Get an Entity from its id + * + * @param int $id + * + * @return null|static + */ + public static function getFromId(int $id): ?self + { + $array = explode('\\', get_called_class()); + $class = end($array); + try { + return DB::findOneBy($class, ['id' => $id]); + } catch (NotFoundException $e) { + return null; + } + } } diff --git a/src/Entity/Poll.php b/src/Entity/Poll.php index 4249eeb636..a8e09a9bcd 100644 --- a/src/Entity/Poll.php +++ b/src/Entity/Poll.php @@ -24,7 +24,6 @@ namespace App\Entity; use App\Core\DB\DB; use App\Core\Entity; use DateTimeInterface; -use function Functional\id; class Poll extends Entity { @@ -36,6 +35,7 @@ class Poll extends Entity private ?string $question; private ?string $options; private DateTimeInterface $created; + private DateTimeInterface $modified; public function setId(int $id): self { @@ -103,6 +103,17 @@ class Poll extends Entity return $this->created; } + public function setModified(DateTimeInterface $modified): self + { + $this->modified = $modified; + return $this; + } + + public function getModified(): DateTimeInterface + { + return $this->modified; + } + // }}} Autocode /** @@ -119,10 +130,11 @@ class Poll extends Entity 'id' => ['type' => 'serial', 'not null' => true], 'uri' => ['type' => 'varchar', 'length' => 191], // 'uri' => ['type' => 'varchar', 'length' => 191, 'not null' => true], - 'gsactor_id' => ['type' => 'int'], //-> gsactor id? + 'gsactor_id' => ['type' => 'int'], 'question' => ['type' => 'text'], 'options' => ['type' => 'text'], - 'created' => ['type' => 'datetime', 'not null' => true], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'], + 'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], ], 'primary key' => ['id'], // 'unique keys' => [ @@ -131,33 +143,6 @@ class Poll extends Entity ]; } - /** - * Get poll object from its id - * - * @param int $id - * - * @return null|static - */ - public static function getFromId(int $id): ?self - { - return DB::find('poll', ['id' => $id]); - } - - /** - * Make new poll object - * - * @param int $gsactorId - * @param string $question - * @param array $opt poll options - * - * @return static poll object - */ - public static function make(int $gsactorId, string $question, array $opt): self - { - $options = implode("\n",$opt); - return self::create(['gsactor_id' => $gsactorId, 'question' => $question, 'options' => $options]); - } - /** * Gets options in array format * @@ -175,11 +160,8 @@ class Poll extends Entity * * @return bool */ - public function isValidSelection($selection): bool + public function isValidSelection(int $selection): bool { - if ($selection != (int) $selection) { - return false; - } if ($selection < 1 || $selection > count($this->getOptionsArr())) { return false; } @@ -196,169 +178,11 @@ class Poll extends Entity $responses = []; $options = $this->getOptionsArr(); for ($i = 0; $i < count($options); ++$i) { - $responses[$options[$i]] = DB::dql('select count(pr) from App\Entity\Poll p, App\Entity\PollResponse pr - where pr.poll_id = :id and pr.selection = :selection', - ['id' => $this->id, 'selection' => $i + 1])[0][1] / $this->id; //todo: fix - - /* - var_dump(DB::dql('select count(pr) from App\Entity\Poll p, App\Entity\PollResponse pr - where pr.poll_id = :id and pr.selection = :selection', - ['id' => $this->id, 'selection' => $i + 1])[0][1]); - */ + $responses[$options[$i]] = DB::dql('select count(pr) from App\Entity\PollResponse pr ' . + 'where pr.poll_id = :id and pr.selection = :selection', + ['id' => $this->id, 'selection' => $i + 1])[0][1]; } return $responses; } - - //from old version - /** - * Get a bookmark based on a notice - * - * @param Notice $notice Notice to check for - * - * @return get_called_class found poll or null - */ - /* - public static function getByNotice($notice) - { - return self::getKV('uri', $notice->uri); - } - */ - - /* - public function getNotice() - { - return Notice::getKV('uri', $this->uri); - } - - public function getUrl() - { - return $this->getNotice()->getUrl(); - }*/ - - /** - * Get the response of a particular user to this poll, if any. - * - * @param Profile $profile - * - * @return get_called_class object or null - */ - /* - public function getResponse(Profile $profile) - { - $pr = Poll_response::pkeyGet(array('poll_id' => $this->id, - 'profile_id' => $profile->id)); - return $pr; - } -*/ - /* - public function countResponses() - { - $pr = new Poll_response(); - $pr->poll_id = $this->id; - $pr->groupBy('selection'); - $pr->selectAdd('count(profile_id) as votes'); - $pr->find(); - - $raw = array(); - while ($pr->fetch()) { - // Votes list 1-based - // Array stores 0-based - $raw[$pr->selection - 1] = $pr->votes; - } - - $counts = array(); - foreach (array_keys($this->getOptions()) as $key) { - if (isset($raw[$key])) { - $counts[$key] = $raw[$key]; - } else { - $counts[$key] = 0; - } - } - return $counts; - }*/ - - /** - * Save a new poll notice - * - * @param Profile $profile - * @param string $question - * @param array $opts (poll responses) - * @param null $options - * - * @throws ClientException - * - * @return Notice saved notice - */ - /* - public static function saveNew($profile, $question, $opts, $options = null) - { - if (empty($options)) { - $options = array(); - } - - $p = new Poll(); - - $p->id = UUID::gen(); - $p->profile_id = $profile->id; - $p->question = $question; - $p->options = implode("\n", $opts); - - if (array_key_exists('created', $options)) { - $p->created = $options['created']; - } else { - $p->created = common_sql_now(); - } - - if (array_key_exists('uri', $options)) { - $p->uri = $options['uri']; - } else { - $p->uri = common_local_url( - 'showpoll', - array('id' => $p->id) - ); - } - - common_log(LOG_DEBUG, "Saving poll: $p->id $p->uri"); - $p->insert(); - - // TRANS: Notice content creating a poll. - // TRANS: %1$s is the poll question, %2$s is a link to the poll. - $content = sprintf( - _m('Poll: %1$s %2$s'), - $question, - $p->uri - ); - $link = '' . htmlspecialchars($question) . ''; - // TRANS: Rendered version of the notice content creating a poll. - // TRANS: %s is a link to the poll with the question as link description. - $rendered = sprintf(_m('Poll: %s'), $link); - - $tags = array('poll'); - $replies = array(); - - $options = array_merge( - array('urls' => array(), - 'rendered' => $rendered, - 'tags' => $tags, - 'replies' => $replies, - 'object_type' => PollPlugin::POLL_OBJECT), - $options - ); - - if (!array_key_exists('uri', $options)) { - $options['uri'] = $p->uri; - } - - $saved = Notice::saveNew( - $profile->id, - $content, - array_key_exists('source', $options) ? - $options['source'] : 'web', - $options - ); - - return $saved; - } - */ } diff --git a/src/Entity/PollResponse.php b/src/Entity/PollResponse.php index 3cf2680677..4db79ac977 100644 --- a/src/Entity/PollResponse.php +++ b/src/Entity/PollResponse.php @@ -35,6 +35,7 @@ class PollResponse extends Entity private ?int $gsactor_id; private ?int $selection; private DateTimeInterface $created; + private DateTimeInterface $modified; public function setId(int $id): self { @@ -102,6 +103,17 @@ class PollResponse extends Entity return $this->created; } + public function setModified(DateTimeInterface $modified): self + { + $this->modified = $modified; + return $this; + } + + public function getModified(): DateTimeInterface + { + return $this->modified; + } + // }}} Autocode /** @@ -121,7 +133,8 @@ class PollResponse extends Entity 'poll_id' => ['type' => 'int', 'length' => 36, 'not null' => true, 'description' => 'UUID of poll being responded to'], 'gsactor_id' => ['type' => 'int'], 'selection' => ['type' => 'int'], - 'created' => ['type' => 'datetime', 'not null' => true], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'], + 'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], ], 'primary key' => ['id'], @@ -129,6 +142,9 @@ class PollResponse extends Entity //'poll_uri_key' => array('uri'), //'poll_response_poll_id_gsactor_id_key' => ['poll_id', 'gsactor_id'], //doctrine bug? ], + 'foreign keys' => [ + 'foreign_poll' => ['poll', ['poll_id' => 'id']], + ], 'indexes' => [ 'poll_response_gsactor_id_poll_id_index' => ['gsactor_id', 'poll_id'], @@ -149,7 +165,6 @@ class PollResponse extends Entity $res = DB::dql('select pr from App\Entity\PollResponse pr where pr.poll_id = :pollId and pr.gsactor_id = :gsactorId', ['pollId' => $pollId, 'gsactorId' => $gsactorId]); - //var_dump( $res); return count($res) != 0; } } diff --git a/src/Util/Common.php b/src/Util/Common.php index 6f38dfce55..d8ce3a5768 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -205,4 +205,18 @@ abstract class Common self::sizeStrToInt(ini_get('memory_limit')) ); } + + /** + * Clamps a value between 2 numbers + * + * @param int $current + * @param int $min + * @param int $max + * + * @return int clamped value + */ + public static function clamp(int $current,int $min,int $max): int + { + return min(max($current,$min),$max); + } } diff --git a/tests/Entity/PollTest.php b/tests/Entity/PollTest.php new file mode 100644 index 0000000000..94e851e4bc --- /dev/null +++ b/tests/Entity/PollTest.php @@ -0,0 +1,38 @@ +. +// }}} + +namespace App\Tests\Util\Form\ActorArrayTransformer; + +use App\Entity\Poll; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +class PollTest extends WebTestCase +{ + public function testPoll() + { + $poll1 = Poll::create(['options' => implode("\n",['option 1', '2nd option'])]); + static::assertSame("option 1\n2nd option", $poll1->getOptions()); + static::assertSame(['option 1', '2nd option'], $poll1->getOptionsArr()); + + static::assertTrue($poll1->isValidSelection(1)); + static::assertTrue($poll1->isValidSelection(2)); + + static::assertFalse($poll1->isValidSelection(0)); + static::assertFalse($poll1->isValidSelection(3)); + } +} diff --git a/tests/Util/Common/CommonTest.php b/tests/Util/Common/CommonTest.php new file mode 100644 index 0000000000..9dee04ba31 --- /dev/null +++ b/tests/Util/Common/CommonTest.php @@ -0,0 +1,33 @@ +. +// }}} + +namespace App\Tests\Util\Common; + +use App\Util\Common; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +class CommonTest extends WebTestCase +{ + public function testClamp() + { + static::assertSame(2, Common::clamp(2,0,3)); + static::assertSame(2, Common::clamp(2,2,3)); + static::assertSame(1, Common::clamp(2,0,1)); + static::assertSame(3, Common::clamp(2,3,5)); + } +}