added drag and drop for new records via calendar (#1962)

This commit is contained in:
Kevin Papst
2020-09-17 01:13:48 +02:00
committed by GitHub
parent 14b3de4300
commit 9ef32e75c5
82 changed files with 2808 additions and 385 deletions

View File

@@ -67,7 +67,15 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
*/
protected function createUrl($url, $json = true)
{
return '/' . ltrim($url, '/') . ($json ? '.json' : '');
if ($json) {
if (stripos($url, '?') !== false) {
$url = str_replace('?', '.json?', $url);
} else {
$url .= '.json';
}
}
return '/' . ltrim($url, '/');
}
protected function assertRequestIsSecured(HttpKernelBrowser $client, string $url, $method = 'GET')
@@ -453,6 +461,15 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
'color' => '@string',
];
case 'ActivityExpanded':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'project' => ['result' => 'object', 'type' => '@ProjectExpanded'],
'color' => '@string',
];
// collection of activities
case 'ActivityCollection':
return [
@@ -502,6 +519,26 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
// TODO new fields: billable, category
];
case 'TimesheetEntityFull':
return [
'id' => 'int',
'begin' => 'DateTime',
'end' => '@DateTime',
'duration' => '@int',
'description' => '@string',
'rate' => 'float',
'activity' => ['result' => 'object', 'type' => 'ActivityExpanded'],
'project' => ['result' => 'object', 'type' => 'ProjectExpanded'],
'tags' => ['result' => 'array', 'type' => 'string'],
'user' => 'int',
'metaFields' => ['result' => 'array', 'type' => 'TimesheetMeta'],
'internalRate' => 'float',
'exported' => 'bool',
'fixedRate' => '@float',
'hourlyRate' => '@float',
// TODO new fields: billable, category
];
case 'TimesheetCollection':
return [
'id' => 'int',
@@ -586,8 +623,6 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
break;
case 'object':
self::assertIsArray($result[$key], sprintf('Key "%s" in type "%s" is not an array', $key, $type));
if ($value['type'][0] === '@') {
if (empty($result[$key])) {
break;
@@ -595,6 +630,8 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
$value['type'] = substr($value['type'], 1);
}
self::assertIsArray($result[$key], sprintf('Key "%s" in type "%s" is not an array', $key, $type));
self::assertApiResponseTypeStructure($value['type'], $result[$key]);
break;

View File

@@ -0,0 +1,89 @@
<?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\API\Serializer;
use App\API\Serializer\ValidationFailedExceptionErrorHandler;
use App\Validator\ValidationFailedException;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Visitor\SerializationVisitorInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @covers \App\API\Serializer\ValidationFailedExceptionErrorHandler
*/
class ValidationFailedExceptionErrorHandlerTest extends TestCase
{
public function testSubscribingMethods()
{
self::assertEquals([[
'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
'type' => 'App\Validator\ValidationFailedException',
'format' => 'json',
'method' => 'serializeExceptionToJson',
]], ValidationFailedExceptionErrorHandler::getSubscribingMethods());
}
public function testWithEmptyConstraintsList()
{
$translator = $this->createMock(TranslatorInterface::class);
$sut = new ValidationFailedExceptionErrorHandler($translator);
$constraints = new ConstraintViolationList();
$validations = new ValidationFailedException($constraints, 'Uuups, that is broken');
$serialization = $this->createMock(SerializationVisitorInterface::class);
$expected = [
'code' => '400',
'message' => null,
'errors' => [
'children' => []
]
];
self::assertEquals($expected, $sut->serializeExceptionToJson($serialization, $validations, []));
}
public function testWithConstraintsList()
{
$translator = $this->createMock(TranslatorInterface::class);
$sut = new ValidationFailedExceptionErrorHandler($translator);
$translator->method('trans')->willReturnArgument(0);
$constraints = new ConstraintViolationList();
$constraints->add(new ConstraintViolation('toooo many tests', 'abc.def', [], '$root', 'begin', 4, null, null, null, '$cause'));
$constraints->add(new ConstraintViolation('missing tests', 'abc.def', [], '$root', 'begin', 4, null, null, null, '$cause'));
$constraints->add(new ConstraintViolation('missing tests', 'test %wuuf% 123', ['%wuuf%' => 'xcv'], '$root', 'end', 4, 3, null, null, '$cause'));
$validations = new ValidationFailedException($constraints, 'Uuups, that is broken');
$serialization = $this->createMock(SerializationVisitorInterface::class);
$expected = [
'code' => '400',
'message' => 'Uuups, that is broken',
'errors' => [
'children' => [
'begin' => [
'errors' => [
0 => 'abc.def',
1 => 'abc.def',
],
],
'end' => [
'errors' => [
0 => 'test %wuuf% 123',
],
]
]
]
];
self::assertEquals($expected, $sut->serializeExceptionToJson($serialization, $validations, []));
}
}

View File

@@ -9,6 +9,7 @@
namespace App\Tests\API;
use App\API\BaseApiController;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
@@ -381,6 +382,30 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertEquals(2016, $result['rate']);
}
public function testPostActionWithFullExpandedResponse()
{
$dateTime = (new UserDateTimeFactoryFactory($this))->create(self::TEST_TIMEZONE);
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$data = [
'activity' => 1,
'project' => 1,
'begin' => ($dateTime->createDateTime('- 16 hours'))->format('Y-m-d H:m:0'),
'end' => ($dateTime->createDateTime())->format('Y-m-d H:m:0'),
'description' => 'foo',
'fixedRate' => 2016,
'hourlyRate' => 127
];
$this->request($client, '/api/timesheets?full=true', 'POST', [], json_encode($data));
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
self::assertApiResponseTypeStructure('TimesheetEntityFull', $result);
$this->assertNotEmpty($result['id']);
$this->assertTrue($result['duration'] == 57600 || $result['duration'] == 57660); // 1 minute rounding might be applied
$this->assertEquals(2016, $result['rate']);
}
public function testPostActionForDifferentUser()
{
$dateTime = (new UserDateTimeFactoryFactory($this))->create(self::TEST_TIMEZONE);
@@ -833,6 +858,38 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertEmpty($timesheet->getTags());
}
public function testRestartActionWithBegin()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$data = [
'description' => 'foo',
'tags' => 'another,testing,bar'
];
$this->request($client, '/api/timesheets/1', 'PATCH', [], json_encode($data));
$begin = new \DateTime('2019-11-27 13:55:00');
$this->request($client, '/api/timesheets/1/restart', 'PATCH', ['begin' => $begin->format(BaseApiController::DATE_FORMAT_PHP)]);
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertEmpty($result['description']);
$this->assertEmpty($result['tags']);
$em = $this->getEntityManager();
/** @var Timesheet $timesheet */
$timesheet = $em->getRepository(Timesheet::class)->find($result['id']);
$this->assertInstanceOf(\DateTime::class, $timesheet->getBegin());
$this->assertEquals($begin->format(BaseApiController::DATE_FORMAT_PHP), $timesheet->getBegin()->format(BaseApiController::DATE_FORMAT_PHP));
$this->assertNull($timesheet->getEnd());
$this->assertEquals(1, $timesheet->getActivity()->getId());
$this->assertEquals(1, $timesheet->getProject()->getId());
$this->assertEmpty($timesheet->getDescription());
$this->assertEmpty($timesheet->getTags());
}
public function testRestartActionWithCopyData()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);