enhanced plugin support (#634)

- refactored admin controller and templates
- plugin support for entity actions
- changed column mail to email in customer table
- refactored theme events
This commit is contained in:
Kevin Papst
2019-03-11 14:46:30 +01:00
committed by GitHub
parent 519be2d5a2
commit 3ac72a5c89
104 changed files with 1878 additions and 976 deletions

View File

@@ -10,6 +10,7 @@
namespace App\Tests\API;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\User;
use App\Repository\Query\VisibilityQuery;
@@ -31,9 +32,11 @@ class ActivityControllerTest extends APIControllerBaseTest
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$project = $em->getRepository(Project::class)->find(1);
$customer = $em->getRepository(Customer::class)->find(1);
$project2 = new Project();
$project2->setName('Activity Test');
$project2->setCustomer($customer);
$em->persist($project2);
$activity = (new Activity())->setName('first one')->setComment('1')->setProject($project2);

View File

@@ -69,7 +69,7 @@ class CustomerControllerTest extends APIControllerBaseTest
if ($full) {
$expectedKeys = [
'id', 'name', 'number', 'comment', 'visible', 'company', 'contact', 'address', 'country', 'currency',
'phone', 'fax', 'mobile', 'mail', 'timezone'
'phone', 'fax', 'mobile', 'email', 'timezone'
];
}

View File

@@ -7,13 +7,12 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Controller\Admin;
namespace App\Tests\Controller;
use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest;
/**
* @coversDefaultClass \App\Controller\Admin\AboutController
* @coversDefaultClass \App\Controller\AboutController
* @group integration
*/
class AboutControllerTest extends ControllerBaseTest

View File

@@ -9,8 +9,13 @@
namespace App\Tests\Controller;
use App\Entity\Activity;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Tests\DataFixtures\ActivityFixtures;
use App\Tests\DataFixtures\ProjectFixtures;
use App\Tests\DataFixtures\TimesheetFixtures;
use Gedmo\Loggable\Entity\LogEntry;
/**
* @coversDefaultClass \App\Controller\ActivityController
@@ -20,29 +25,229 @@ class ActivityControllerTest extends ControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/activities/recent');
$this->assertUrlIsSecured('/admin/activity/');
$this->assertUrlIsSecuredForRole(User::ROLE_TEAMLEAD, '/admin/activity/');
}
public function testRecentActivitiesAction()
public function testIndexAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertAccessIsGranted($client, '/admin/activity/');
$this->assertHasDataTable($client);
}
public function testCreateAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertAccessIsGranted($client, '/admin/activity/create');
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]'));
$this->assertNull($form->get('activity_edit_form[create_more]')->getValue());
$client->submit($form, [
'activity_edit_form' => [
'name' => 'An AcTiVitY Name',
'project' => '1',
]
]);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->request($client, '/admin/activity/2/edit');
$editForm = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertEquals('An AcTiVitY Name', $editForm->get('activity_edit_form[name]')->getValue());
// make sure customer and project are pre-selected for none global activities
$this->assertEquals('1', $editForm->get('activity_edit_form[project]')->getValue());
$this->assertEquals('1', $editForm->get('activity_edit_form[customer]')->getValue());
}
public function testCreateActionWithCreateMore()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$user = $this->getUserByRole($em, User::ROLE_USER);
$fixture = new TimesheetFixtures();
$fixture->setUser($user);
$fixture->setAmount(1);
$fixture->setStartDate(new \DateTime('-10 days'));
$fixture = new ProjectFixtures();
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$this->request($client, '/activities/recent');
$this->assertAccessIsGranted($client, '/admin/activity/create');
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]'));
/** @var \Symfony\Component\DomCrawler\Field\ChoiceFormField $project */
$project = $form->get('activity_edit_form[project]');
$options = $project->availableOptionValues();
$selectedProject = $options[array_rand($options)];
$client->submit($form, [
'activity_edit_form' => [
'name' => 'Test create more',
'create_more' => true,
'project' => $selectedProject,
]
]);
$this->assertFalse($client->getResponse()->isRedirect());
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]'));
$this->assertEquals(1, $form->get('activity_edit_form[create_more]')->getValue());
$this->assertEquals($selectedProject, $form->get('activity_edit_form[project]')->getValue());
}
public function testEditAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertAccessIsGranted($client, '/admin/activity/1/edit');
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertFalse($form->has('activity_edit_form[create_more]'));
$this->assertEquals('Test', $form->get('activity_edit_form[name]')->getValue());
$client->submit($form, [
'activity_edit_form' => ['name' => 'Test 2']
]);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->request($client, '/admin/activity/1/edit');
$editForm = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertEquals('Test 2', $editForm->get('activity_edit_form[name]')->getValue());
// make sure no customer or project is pre-selected for global activities
$this->assertEquals('', $editForm->get('activity_edit_form[customer]')->getValue());
$this->assertEquals('', $editForm->get('activity_edit_form[project]')->getValue());
}
public function testDeleteAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->request($client, '/admin/activity/1/edit');
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
$this->request($client, '/admin/activity/1/delete');
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasFlashDeleteSuccess($client);
$this->assertHasNoEntriesWithFilter($client);
$this->assertContains('<li class="dropdown notifications-menu">', $content);
$this->assertContains('<span class="label label-success">1</span>', $content);
$this->assertContains('<a href="/en/timesheet/start/1">', $content);
$this->request($client, '/admin/activity/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
public function testDeleteActionWithTimesheetEntries()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new TimesheetFixtures();
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$timesheets = $em->getRepository(Timesheet::class)->findAll();
$this->assertEquals(10, count($timesheets));
/** @var Timesheet $entry */
foreach ($timesheets as $entry) {
$this->assertEquals(1, $entry->getActivity()->getId());
}
$this->request($client, '/admin/activity/1/delete');
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=form]')->form();
$this->assertStringEndsWith($this->createUrl('/admin/activity/1/delete'), $form->getUri());
$client->submit($form);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasFlashDeleteSuccess($client);
$this->assertHasNoEntriesWithFilter($client);
// SQLIte does not necessarly support onCascade delete, so these timesheet will stay after deletion
// $em->clear();
// $timesheets = $em->getRepository(Timesheet::class)->findAll();
// $this->assertEquals(0, count($timesheets));
$this->request($client, '/admin/activity/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
public function testDeleteActionWithTimesheetEntriesAndReplacement()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new TimesheetFixtures();
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$fixture = new ActivityFixtures();
$fixture->setAmount(1)->setIsGlobal(true)->setIsVisible(true);
$this->importFixture($em, $fixture);
$timesheets = $em->getRepository(Timesheet::class)->findAll();
$this->assertEquals(10, count($timesheets));
/** @var Timesheet $entry */
foreach ($timesheets as $entry) {
$this->assertEquals(1, $entry->getActivity()->getId());
}
$this->request($client, '/admin/activity/1/delete');
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=form]')->form();
$this->assertStringEndsWith($this->createUrl('/admin/activity/1/delete'), $form->getUri());
$client->submit($form, [
'form' => [
'activity' => 2
]
]);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
$timesheets = $em->getRepository(Timesheet::class)->findAll();
$this->assertEquals(10, count($timesheets));
/** @var Timesheet $entry */
foreach ($timesheets as $entry) {
$this->assertEquals(2, $entry->getActivity()->getId());
}
$this->request($client, '/admin/activity/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
/**
* @dataProvider getValidationTestData
*/
public function testValidationForCreateAction(array $formData, array $validationFields)
{
$this->assertFormHasValidationError(
User::ROLE_ADMIN,
'/admin/activity/create',
'form[name=activity_edit_form]',
$formData,
$validationFields
);
}
public function getValidationTestData()
{
return [
[
[
'activity_edit_form' => [
'name' => '',
'project' => 0,
]
],
[
'#activity_edit_form_name',
'#activity_edit_form_project',
]
],
];
}
}

View File

@@ -1,252 +0,0 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Controller\Admin;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest;
use App\Tests\DataFixtures\ActivityFixtures;
use App\Tests\DataFixtures\ProjectFixtures;
use App\Tests\DataFixtures\TimesheetFixtures;
/**
* @coversDefaultClass \App\Controller\Admin\ActivityController
* @group integration
*/
class ActivityControllerTest extends ControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/admin/activity/');
$this->assertUrlIsSecuredForRole(User::ROLE_TEAMLEAD, '/admin/activity/');
}
public function testIndexAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertAccessIsGranted($client, '/admin/activity/');
$this->assertHasDataTable($client);
}
public function testCreateAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertAccessIsGranted($client, '/admin/activity/create');
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]'));
$this->assertNull($form->get('activity_edit_form[create_more]')->getValue());
$client->submit($form, [
'activity_edit_form' => [
'name' => 'An AcTiVitY Name',
'project' => '1',
]
]);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->request($client, '/admin/activity/2/edit');
$editForm = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertEquals('An AcTiVitY Name', $editForm->get('activity_edit_form[name]')->getValue());
// make sure customer and project are pre-selected for none global activities
$this->assertEquals('1', $editForm->get('activity_edit_form[project]')->getValue());
$this->assertEquals('1', $editForm->get('activity_edit_form[customer]')->getValue());
}
public function testCreateActionWithCreateMore()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new ProjectFixtures();
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$this->assertAccessIsGranted($client, '/admin/activity/create');
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]'));
/** @var \Symfony\Component\DomCrawler\Field\ChoiceFormField $project */
$project = $form->get('activity_edit_form[project]');
$options = $project->availableOptionValues();
$selectedProject = $options[array_rand($options)];
$client->submit($form, [
'activity_edit_form' => [
'name' => 'Test create more',
'create_more' => true,
'project' => $selectedProject,
]
]);
$this->assertFalse($client->getResponse()->isRedirect());
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertTrue($form->has('activity_edit_form[create_more]'));
$this->assertEquals(1, $form->get('activity_edit_form[create_more]')->getValue());
$this->assertEquals($selectedProject, $form->get('activity_edit_form[project]')->getValue());
}
public function testEditAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->assertAccessIsGranted($client, '/admin/activity/1/edit');
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertFalse($form->has('activity_edit_form[create_more]'));
$this->assertEquals('Test', $form->get('activity_edit_form[name]')->getValue());
$client->submit($form, [
'activity_edit_form' => ['name' => 'Test 2']
]);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->request($client, '/admin/activity/1/edit');
$editForm = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
$this->assertEquals('Test 2', $editForm->get('activity_edit_form[name]')->getValue());
// make sure no customer or project is pre-selected for global activities
$this->assertEquals('', $editForm->get('activity_edit_form[customer]')->getValue());
$this->assertEquals('', $editForm->get('activity_edit_form[project]')->getValue());
}
public function testDeleteAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->request($client, '/admin/activity/1/edit');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->request($client, '/admin/activity/1/delete');
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasFlashDeleteSuccess($client);
$this->assertHasNoEntriesWithFilter($client);
$this->request($client, '/admin/activity/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
public function testDeleteActionWithTimesheetEntries()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new TimesheetFixtures();
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$timesheets = $em->getRepository(Timesheet::class)->findAll();
$this->assertEquals(10, count($timesheets));
/** @var Timesheet $entry */
foreach ($timesheets as $entry) {
$this->assertEquals(1, $entry->getActivity()->getId());
}
$this->request($client, '/admin/activity/1/delete');
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=form]')->form();
$this->assertStringEndsWith($this->createUrl('/admin/activity/1/delete'), $form->getUri());
$client->submit($form);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasFlashDeleteSuccess($client);
$this->assertHasNoEntriesWithFilter($client);
// SQLIte does not necessarly support onCascade delete, so these timesheet will stay after deletion
// $em->clear();
// $timesheets = $em->getRepository(Timesheet::class)->findAll();
// $this->assertEquals(0, count($timesheets));
$this->request($client, '/admin/activity/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
public function testDeleteActionWithTimesheetEntriesAndReplacement()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new TimesheetFixtures();
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
$fixture->setAmount(10);
$this->importFixture($em, $fixture);
$fixture = new ActivityFixtures();
$fixture->setAmount(1)->setIsGlobal(true)->setIsVisible(true);
$this->importFixture($em, $fixture);
$timesheets = $em->getRepository(Timesheet::class)->findAll();
$this->assertEquals(10, count($timesheets));
/** @var Timesheet $entry */
foreach ($timesheets as $entry) {
$this->assertEquals(1, $entry->getActivity()->getId());
}
$this->request($client, '/admin/activity/1/delete');
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $client->getCrawler()->filter('form[name=form]')->form();
$this->assertStringEndsWith($this->createUrl('/admin/activity/1/delete'), $form->getUri());
$client->submit($form, [
'form' => [
'activity' => 2
]
]);
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
$client->followRedirect();
$this->assertHasDataTable($client);
$this->assertHasFlashSuccess($client);
$timesheets = $em->getRepository(Timesheet::class)->findAll();
$this->assertEquals(10, count($timesheets));
/** @var Timesheet $entry */
foreach ($timesheets as $entry) {
$this->assertEquals(2, $entry->getActivity()->getId());
}
$this->request($client, '/admin/activity/1/edit');
$this->assertFalse($client->getResponse()->isSuccessful());
}
/**
* @dataProvider getValidationTestData
*/
public function testValidationForCreateAction(array $formData, array $validationFields)
{
$this->assertFormHasValidationError(
User::ROLE_ADMIN,
'/admin/activity/create',
'form[name=activity_edit_form]',
$formData,
$validationFields
);
}
public function getValidationTestData()
{
return [
[
[
'activity_edit_form' => [
'name' => '',
'project' => 0,
]
],
[
'#activity_edit_form_name',
'#activity_edit_form_project',
]
],
];
}
}

View File

@@ -137,7 +137,20 @@ abstract class ControllerBaseTest extends WebTestCase
$client->getResponse()->isSuccessful(),
sprintf('The secure URL %s is not protected for role %s', $url, $role)
);
$this->assertContains('Symfony\Component\Security\Core\Exception\AccessDeniedException', $client->getResponse()->getContent());
$this->assertAccessDenied($client);
}
protected function assertAccessDenied(Client $client)
{
$this->assertFalse(
$client->getResponse()->isSuccessful(),
'Access is not denied for URL: ' . $client->getRequest()->getUri()
);
$this->assertContains(
'Symfony\Component\Security\Core\Exception\AccessDeniedException',
$client->getResponse()->getContent(),
'Could not find AccessDeniedException in response'
);
}
/**
@@ -231,10 +244,22 @@ abstract class ControllerBaseTest extends WebTestCase
}
}
/**
* @param Client $client
*/
protected function assertHasNoEntriesWithFilter(Client $client)
{
$this->assertCalloutWidgetWithMessage($client, 'No entries were found based on your selected filters.');
}
/**
* @param Client $client
* @param string $message
*/
protected function assertCalloutWidgetWithMessage(Client $client, string $message)
{
$node = $client->getCrawler()->filter('div.callout.callout-warning.lead');
$this->assertContains('No entries were found based on your selected filters.', $node->text());
$this->assertContains($message, $node->text());
}
/**

View File

@@ -7,16 +7,17 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Controller\Admin;
namespace App\Tests\Controller;
use App\Entity\Customer;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest;
use App\Tests\DataFixtures\CustomerFixtures;
use App\Tests\DataFixtures\TimesheetFixtures;
use Gedmo\Loggable\Entity\LogEntry;
/**
* @coversDefaultClass \App\Controller\Admin\CustomerController
* @coversDefaultClass \App\Controller\CustomerController
* @group integration
*/
class CustomerControllerTest extends ControllerBaseTest

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Controller;
use App\Entity\User;
use App\Tests\DataFixtures\TimesheetFixtures;
/**
* @coversDefaultClass \App\Controller\NavbarController
* @group integration
*/
class NavbarControllerTest extends ControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/activities/recent');
}
public function testRecentActivitiesAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$user = $this->getUserByRole($em, User::ROLE_USER);
$fixture = new TimesheetFixtures();
$fixture->setUser($user);
$fixture->setAmount(1);
$fixture->setStartDate(new \DateTime('-10 days'));
$this->importFixture($em, $fixture);
$this->request($client, '/activities/recent');
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
$this->assertContains('<li class="dropdown notifications-menu">', $content);
$this->assertContains('<span class="label label-success">1</span>', $content);
$this->assertContains('<a href="/en/timesheet/start/1">', $content);
}
}

View File

@@ -7,17 +7,18 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Controller\Admin;
namespace App\Tests\Controller;
use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest;
use App\Tests\DataFixtures\CustomerFixtures;
use App\Tests\DataFixtures\ProjectFixtures;
use App\Tests\DataFixtures\TimesheetFixtures;
use Gedmo\Loggable\Entity\LogEntry;
/**
* @coversDefaultClass \App\Controller\Admin\ProjectController
* @coversDefaultClass \App\Controller\ProjectController
* @group integration
*/
class ProjectControllerTest extends ControllerBaseTest

View File

@@ -14,12 +14,13 @@ use App\Entity\User;
use App\Form\Type\DateRangeType;
use App\Tests\Controller\ControllerBaseTest;
use App\Tests\DataFixtures\TimesheetFixtures;
use Gedmo\Loggable\Entity\LogEntry;
/**
* @coversDefaultClass \App\Controller\Admin\TimesheetController
* @coversDefaultClass \App\Controller\TimesheetTeamController
* @group integration
*/
class TimesheetControllerTest extends ControllerBaseTest
class TimesheetTeamControllerTest extends ControllerBaseTest
{
public function testIsSecure()
{

View File

@@ -7,13 +7,12 @@
* file that was distributed with this source code.
*/
namespace App\Tests\Controller\Admin;
namespace App\Tests\Controller;
use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest;
/**
* @coversDefaultClass \App\Controller\Admin\UserController
* @coversDefaultClass \App\Controller\UserController
* @group integration
*/
class UserControllerTest extends ControllerBaseTest

View File

@@ -14,6 +14,7 @@ use App\Entity\Project;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Entity\UserPreference;
use App\Timesheet\Util;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Factory;
@@ -271,7 +272,8 @@ class TimesheetFixtures extends Fixture
$end = $end->modify('+ ' . (rand(1, 172800)) . ' seconds');
$duration = $end->getTimestamp() - $start->getTimestamp();
$rate = $user->getPreferenceValue(UserPreference::HOURLY_RATE);
$hourlyRate = (float) $user->getPreferenceValue(UserPreference::HOURLY_RATE);
$rate = Util::calculateRate($hourlyRate, $duration);
$entry = new Timesheet();
$entry
@@ -279,7 +281,7 @@ class TimesheetFixtures extends Fixture
->setProject($project)
->setDescription($description)
->setUser($user)
->setRate(round(($duration / 3600) * $rate))
->setRate($rate)
->setBegin($start);
if ($this->fixedRate) {
@@ -287,13 +289,14 @@ class TimesheetFixtures extends Fixture
}
if ($this->hourlyRate) {
$entry->setHourlyRate($rate);
$entry->setHourlyRate($hourlyRate);
}
if ($setEndDate) {
$entry
->setEnd($end)
->setDuration($duration);
->setDuration($duration)
;
}
return $entry;

View File

@@ -36,7 +36,7 @@ class CustomerTest extends AbstractEntityTest
$this->assertNull($sut->getPhone());
$this->assertNull($sut->getFax());
$this->assertNull($sut->getMobile());
$this->assertNull($sut->getMail());
$this->assertNull($sut->getEmail());
$this->assertNull($sut->getHomepage());
$this->assertNull($sut->getTimezone());
@@ -76,8 +76,8 @@ class CustomerTest extends AbstractEntityTest
$this->assertInstanceOf(Customer::class, $sut->setMobile('76576534'));
$this->assertEquals('76576534', $sut->getMobile());
$this->assertInstanceOf(Customer::class, $sut->setMail('test@example.com'));
$this->assertEquals('test@example.com', $sut->getMail());
$this->assertInstanceOf(Customer::class, $sut->setEmail('test@example.com'));
$this->assertEquals('test@example.com', $sut->getEmail());
$this->assertInstanceOf(Customer::class, $sut->setHomepage('https://www.example.com'));
$this->assertEquals('https://www.example.com', $sut->getHomepage());

View File

@@ -23,8 +23,6 @@ use PHPUnit\Framework\TestCase;
*/
class RateCalculatorTest extends TestCase
{
public const HOURLY_RATE = 75;
public function testCalculateWithTimesheetHourlyRate()
{
$record = new Timesheet();
@@ -130,7 +128,7 @@ class RateCalculatorTest extends TestCase
$this->assertEquals($exptectedRate, $timesheet->getRate());
}
protected function getTestUser($rate = self::HOURLY_RATE)
protected function getTestUser($rate = 75)
{
$pref = new UserPreference();
$pref->setName(UserPreference::HOURLY_RATE);
@@ -159,23 +157,23 @@ class RateCalculatorTest extends TestCase
}
/**
* Uses the hourly rate from user_preferences to calculate the rate.
*
* @dataProvider getRuleDefinitions
*/
public function testCalculateWithRules($rules, $expectedFactor)
public function testCalculateWithRulesByUsersHourlyRate($duration, $rules, $expectedRate)
{
$seconds = 31837;
$end = new \DateTime();
$end->setTimezone(new \DateTimeZone('UTC'));
$end->setTime(12, 0, 0);
$start = clone $end;
$start->setTimezone(new \DateTimeZone('UTC'));
$start->setTimestamp($end->getTimestamp() - $seconds);
$start->setTimestamp($end->getTimestamp() - $duration);
$record = new Timesheet();
$record->setUser($this->getTestUser());
$record->setBegin($start);
$record->setDuration($seconds);
$record->setDuration($duration);
$record->setActivity(new Activity());
$this->assertEquals(0, $record->getRate());
@@ -185,10 +183,7 @@ class RateCalculatorTest extends TestCase
$sut = new RateCalculator($rules);
$sut->calculate($record);
$this->assertEquals(
$this->rateForSeconds(self::HOURLY_RATE, $seconds) * $expectedFactor,
$record->getRate()
);
$this->assertEquals($expectedRate, $record->getRate());
}
public function getRuleDefinitions()
@@ -200,10 +195,12 @@ class RateCalculatorTest extends TestCase
return [
[
31837,
[],
1
663.27
],
[
31837,
[
'default' => [
'days' => [$day],
@@ -214,9 +211,10 @@ class RateCalculatorTest extends TestCase
'factor' => 1.5
],
],
2.0
1326.54
],
[
31837,
[
'default' => [
'days' => [$day],
@@ -227,13 +225,8 @@ class RateCalculatorTest extends TestCase
'factor' => 1.5
],
],
3.5
2321.45
],
];
}
protected function rateForSeconds($hourlyRate, $seconds)
{
return (float) $hourlyRate * ($seconds / 3600);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Timesheet;
use App\Timesheet\Util;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Timesheet\Util
*/
class UtilTest extends TestCase
{
/**
* @dataProvider getRateCalculationData
*/
public function testCalculateRate($hourlyRate, $duration, $expectedRate)
{
$this->assertEquals($expectedRate, Util::calculateRate($hourlyRate, $duration));
}
public function getRateCalculationData()
{
yield [0, 0, 0];
yield [1, 100, 0.03];
yield [1, 900, 0.25];
yield [1, 1800, 0.5];
yield [10000, 1, 2.78];
yield [736, 123.45, 25.15];
yield [736, 123, 25.15];
yield [7360, 1234.99, 2522.84];
yield [7360, 1234, 2522.84];
yield [7360.34, 1234, 2522.96];
yield [7360.01, 1234, 2522.85];
yield [7360.99, 1234, 2523.18];
}
}

View File

@@ -40,7 +40,7 @@ class DateExtensionsTest extends TestCase
public function testGetFilters()
{
$filters = ['month_name', 'date_short', 'date_time', 'date_format', 'time', 'hour24'];
$filters = ['month_name', 'date_short', 'date_time', 'date_full', 'date_format', 'time', 'hour24'];
$sut = $this->getSut('de', []);
$twigFilters = $sut->getFilters();
$this->assertCount(count($filters), $twigFilters);
@@ -148,4 +148,17 @@ class DateExtensionsTest extends TestCase
]);
$this->assertEquals('foo', $sut->hour24('foo', 'bar'));
}
public function testDateTimeFull()
{
$sut = $this->getSut('en', [
'en' => ['date_time_type' => 'yyyy-MM-dd HH:mm:ss'],
]);
$dateTime = new \DateTime('2019-08-17 12:29:47', new \DateTimeZone(date_default_timezone_get()));
$dateTime->setDate(2019, 8, 17);
$dateTime->setTime(12, 29, 47);
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull($dateTime));
}
}

View File

@@ -10,6 +10,7 @@
namespace App\Tests\Twig;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Twig\Extensions;
use App\Utils\LocaleSettings;
use PHPUnit\Framework\TestCase;
@@ -60,7 +61,7 @@ class ExtensionsTest extends TestCase
public function testGetFunctions()
{
$functions = ['locales', 'is_visible_column', 'is_datatable_configured'];
$functions = ['locales', 'is_visible_column', 'is_datatable_configured', 'class_name'];
$sut = $this->getSut($this->localeDe);
$twigFunctions = $sut->getFunctions();
$this->assertCount(count($functions), $twigFunctions);
@@ -244,4 +245,14 @@ class ExtensionsTest extends TestCase
$this->assertEquals($expected, $result);
}
}
public function testGetClassName()
{
$sut = $this->getSut($this->localeEn);
$this->assertEquals('DateTime', $sut->getClassName(new \DateTime()));
$this->assertEquals('stdClass', $sut->getClassName(new \stdClass()));
$this->assertNull($sut->getClassName(''));
$this->assertNull($sut->getClassName(null));
$this->assertEquals('App\Entity\User', $sut->getClassName(new User()));
}
}