configurable csv/xlsx export templates (#5531)
This commit is contained in:
@@ -48,7 +48,7 @@ class ApiDocControllerTest extends AbstractControllerBaseTestCase
|
||||
}
|
||||
}
|
||||
|
||||
$expectedKeys = ['Actions', 'Activity', 'Default', 'Customer', 'Project', 'Tag', 'Team', 'Timesheet', 'User', 'Invoice'];
|
||||
$expectedKeys = ['Actions', 'Activity', 'Default', 'Customer', 'Project', 'Tag', 'Team', 'Timesheet', 'User', 'Invoice', 'Export'];
|
||||
$actual = array_keys($tags);
|
||||
|
||||
sort($actual);
|
||||
@@ -73,6 +73,7 @@ class ApiDocControllerTest extends AbstractControllerBaseTestCase
|
||||
'/api/customers/{id}/meta',
|
||||
'/api/customers/{id}/rates',
|
||||
'/api/customers/{id}/rates/{rateId}',
|
||||
'/api/export/{id}',
|
||||
'/api/invoices',
|
||||
'/api/invoices/{id}',
|
||||
'/api/projects',
|
||||
|
||||
80
tests/API/ExportControllerTest.php
Normal file
80
tests/API/ExportControllerTest.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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;
|
||||
|
||||
use App\Entity\ExportTemplate;
|
||||
use App\Entity\User;
|
||||
use App\Repository\ExportTemplateRepository;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
class ExportControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
private function importExportTemplate(): ExportTemplate
|
||||
{
|
||||
/** @var ExportTemplateRepository $repository */
|
||||
$repository = $this->getEntityManager()->getRepository(ExportTemplate::class);
|
||||
|
||||
$template = new ExportTemplate();
|
||||
$template->setRenderer('csv');
|
||||
$template->setTitle('csv');
|
||||
$template->setColumns(['activity.name', 'project.number', 'project.name', 'customer.name', 'user.account_number', 'duration', 'date', 'rate', 'currency']);
|
||||
$template->setLanguage('en');
|
||||
$template->setLanguage('en');
|
||||
|
||||
$repository->saveExportTemplate($template);
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
public function testDeleteIsSecure(): void
|
||||
{
|
||||
$this->assertUrlIsSecured('/api/export/1', Request::METHOD_DELETE);
|
||||
}
|
||||
|
||||
public function testDeleteActionWithUnknownTemplate(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertNotFoundForDelete($client, '/api/export/' . PHP_INT_MAX);
|
||||
}
|
||||
|
||||
public function testDeleteEntityIsSecure(): void
|
||||
{
|
||||
$client = $this->createClient();
|
||||
$template = $this->importExportTemplate();
|
||||
|
||||
$this->assertRequestIsSecured($client, '/api/export/' . $template->getId(), Request::METHOD_DELETE);
|
||||
}
|
||||
|
||||
public function testDeleteActionWithoutAuthorization(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
$template = $this->importExportTemplate();
|
||||
|
||||
$this->request($client, '/api/export/' . $template->getId(), Request::METHOD_DELETE);
|
||||
|
||||
$response = $client->getResponse();
|
||||
$this->assertApiResponseAccessDenied($response);
|
||||
}
|
||||
|
||||
public function testDeleteAction(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$template = $this->importExportTemplate();
|
||||
|
||||
$this->request($client, '/api/export/' . $template->getId(), Request::METHOD_DELETE);
|
||||
self::assertTrue($client->getResponse()->isSuccessful());
|
||||
self::assertEquals(Response::HTTP_NO_CONTENT, $client->getResponse()->getStatusCode());
|
||||
self::assertEmpty($client->getResponse()->getContent());
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,14 @@
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\ExportTemplate;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Tests\DataFixtures\ExportTemplateFixtures;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Symfony\Component\DomCrawler\Field\FormField;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
@@ -25,7 +28,7 @@ class ExportControllerTest extends AbstractControllerBaseTestCase
|
||||
$this->assertUrlIsSecured('/export/');
|
||||
}
|
||||
|
||||
public function testIsSecureForrole(): void
|
||||
public function testIsSecureForRole(): void
|
||||
{
|
||||
$this->assertUrlIsSecuredForRole(User::ROLE_USER, '/export/');
|
||||
}
|
||||
@@ -90,6 +93,16 @@ class ExportControllerTest extends AbstractControllerBaseTestCase
|
||||
// +1 row for summary
|
||||
$this->assertDataTableRowCount($client, 'datatable_export', 22);
|
||||
|
||||
$header = $client->getCrawler()->filter('section.content div.datatable_export table.dataTable thead th');
|
||||
$titles = [];
|
||||
/** @var \DOMElement $th */
|
||||
foreach ($header as $th) {
|
||||
$titles[] = trim($th->textContent);
|
||||
}
|
||||
self::assertEquals([
|
||||
'', 'Date', 'User', 'Project', 'Activity', 'Description', 'Tags', 'Duration', 'Unit price', 'Internal price', 'Total price', '',
|
||||
], $titles);
|
||||
|
||||
// assert export type buttons are available
|
||||
$expected = [
|
||||
'csv' => 'csv',
|
||||
@@ -246,4 +259,71 @@ class ExportControllerTest extends AbstractControllerBaseTestCase
|
||||
self::assertTrue($timesheet->isExported());
|
||||
}
|
||||
}
|
||||
|
||||
public function testCreateTemplateIsSecure(): void
|
||||
{
|
||||
$this->assertUrlIsSecured('/export/template-create');
|
||||
}
|
||||
|
||||
public function testCreateTemplateIsSecureForRole(): void
|
||||
{
|
||||
$this->assertUrlIsSecuredForRole(User::ROLE_USER, '/export/template-create');
|
||||
}
|
||||
|
||||
public function testCreateTemplateAction(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/export/template-create');
|
||||
$form = $client->getCrawler()->filter('form[name=export_template_spreadsheet_form]')->form();
|
||||
$client->submit($form, [
|
||||
'export_template_spreadsheet_form' => [
|
||||
'title' => 'My temaplte name',
|
||||
'renderer' => 'xlsx',
|
||||
'language' => 'de',
|
||||
'columns' => 'date',
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertIsRedirect($client, $this->createUrl('/export/'));
|
||||
|
||||
$templates = $this->getEntityManager()->getRepository(ExportTemplate::class)->findAll();
|
||||
self::assertCount(1, $templates);
|
||||
$template = array_pop($templates);
|
||||
$id = $template->getId();
|
||||
|
||||
$this->request($client, $this->createUrl('/export/template-edit/' . $id));
|
||||
self::assertTrue($client->getResponse()->isSuccessful());
|
||||
$editForm = $client->getCrawler()->filter('form[name=export_template_spreadsheet_form]')->form();
|
||||
$field = $editForm->get('export_template_spreadsheet_form[title]');
|
||||
self::assertInstanceOf(FormField::class, $field);
|
||||
self::assertEquals('My temaplte name', $field->getValue());
|
||||
}
|
||||
|
||||
public function testEditTemplateAction(): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
/** @var ExportTemplate[] $templates */
|
||||
$templates = $this->importFixture(new ExportTemplateFixtures());
|
||||
|
||||
$id = $templates[0]->getId();
|
||||
|
||||
$this->request($client, $this->createUrl('/export/template-edit/' . $id));
|
||||
self::assertTrue($client->getResponse()->isSuccessful());
|
||||
$form = $client->getCrawler()->filter('form[name=export_template_spreadsheet_form]')->form();
|
||||
$field = $form->get('export_template_spreadsheet_form[title]');
|
||||
self::assertInstanceOf(FormField::class, $field);
|
||||
self::assertEquals('CSV Test', $field->getValue());
|
||||
|
||||
$client->submit($form, [
|
||||
'export_template_spreadsheet_form' => [
|
||||
'title' => 'My temaplte name',
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertIsRedirect($client, $this->createUrl('/export/'));
|
||||
|
||||
/** @var ExportTemplate $template */
|
||||
$template = $this->getEntityManager()->getRepository(ExportTemplate::class)->find($id);
|
||||
self::assertEquals('My temaplte name', $template->getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class PermissionControllerTest extends AbstractControllerBaseTestCase
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/permissions');
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 135);
|
||||
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 136);
|
||||
$this->assertPageActions($client, [
|
||||
'create modal-ajax-form' => $this->createUrl('/admin/permissions/roles/create'),
|
||||
]);
|
||||
|
||||
@@ -35,12 +35,9 @@ class CustomerServiceTest extends TestCase
|
||||
private function getSut(
|
||||
?EventDispatcherInterface $dispatcher = null,
|
||||
?ValidatorInterface $validator = null,
|
||||
?CustomerRepository $repository = null,
|
||||
?SystemConfiguration $configuration = null
|
||||
): CustomerService {
|
||||
if ($repository === null) {
|
||||
$repository = $this->createMock(CustomerRepository::class);
|
||||
}
|
||||
$repository = $this->createMock(CustomerRepository::class);
|
||||
|
||||
if ($dispatcher === null) {
|
||||
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
@@ -163,7 +160,7 @@ class CustomerServiceTest extends TestCase
|
||||
]
|
||||
]);
|
||||
|
||||
$sut = $this->getSut(null, null, null, $configuration);
|
||||
$sut = $this->getSut(null, null, $configuration);
|
||||
$customer = $sut->createNewCustomer('Test');
|
||||
|
||||
self::assertEquals((string) $expected, $customer->getNumber());
|
||||
|
||||
40
tests/DataFixtures/ExportTemplateFixtures.php
Normal file
40
tests/DataFixtures/ExportTemplateFixtures.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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\DataFixtures;
|
||||
|
||||
use App\Entity\ExportTemplate;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
final class ExportTemplateFixtures implements TestFixture
|
||||
{
|
||||
/**
|
||||
* @return ExportTemplate[]
|
||||
*/
|
||||
public function load(ObjectManager $manager): array
|
||||
{
|
||||
$template1 = new ExportTemplate();
|
||||
$template1->setRenderer('csv');
|
||||
$template1->setLanguage('de');
|
||||
$template1->setTitle('CSV Test');
|
||||
$template1->setColumns(['date', 'user.name', 'duration', 'customer.name']);
|
||||
$manager->persist($template1);
|
||||
|
||||
$template2 = new ExportTemplate();
|
||||
$template2->setRenderer('xlsx');
|
||||
$template2->setLanguage('en');
|
||||
$template2->setTitle('Excel Test');
|
||||
$template2->setColumns(['date', 'user.name', 'duration_seconds', 'project.name']);
|
||||
$manager->persist($template2);
|
||||
|
||||
$manager->flush();
|
||||
|
||||
return [$template1, $template2];
|
||||
}
|
||||
}
|
||||
73
tests/Entity/ExportTemplateTest.php
Normal file
73
tests/Entity/ExportTemplateTest.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?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\Entity;
|
||||
|
||||
use App\Entity\ExportTemplate;
|
||||
|
||||
/**
|
||||
* @covers \App\Entity\ExportTemplate
|
||||
*/
|
||||
class ExportTemplateTest extends AbstractEntityTestCase
|
||||
{
|
||||
public function testDefaultValues(): void
|
||||
{
|
||||
$sut = new ExportTemplate();
|
||||
self::assertNull($sut->getId());
|
||||
self::assertNull($sut->getTitle());
|
||||
self::assertEquals('csv', $sut->getRenderer());
|
||||
self::assertNull($sut->getLanguage());
|
||||
self::assertEquals([], $sut->getColumns());
|
||||
self::assertEquals([], $sut->getOptions());
|
||||
}
|
||||
|
||||
public function testSetter(): void
|
||||
{
|
||||
$sut = new ExportTemplate();
|
||||
self::assertEquals('New', (string) $sut);
|
||||
|
||||
$sut->setTitle('foo');
|
||||
self::assertEquals('foo', $sut->getTitle());
|
||||
self::assertEquals('foo', (string) $sut);
|
||||
$sut->setTitle(null);
|
||||
self::assertNull($sut->getTitle());
|
||||
self::assertEquals('New', (string) $sut);
|
||||
|
||||
$sut->setRenderer('xlsx');
|
||||
self::assertEquals('xlsx', $sut->getRenderer());
|
||||
|
||||
$sut->setLanguage('de');
|
||||
self::assertEquals('de', $sut->getLanguage());
|
||||
$sut->setLanguage(null);
|
||||
self::assertNull($sut->getLanguage());
|
||||
|
||||
$sut->setColumns(['foo', 'bar', 'WORLD']);
|
||||
self::assertEquals(['foo', 'bar', 'WORLD'], $sut->getColumns());
|
||||
$sut->setColumns(null);
|
||||
self::assertEquals([], $sut->getColumns());
|
||||
|
||||
$sut->setOptions(['foo' => 1, 'bar' => true, 'WORLD' => 'HELLO']);
|
||||
self::assertEquals(['foo' => 1, 'bar' => true, 'WORLD' => 'HELLO'], $sut->getOptions());
|
||||
$sut->setOptions(null);
|
||||
self::assertEquals([], $sut->getOptions());
|
||||
}
|
||||
|
||||
public function testClone(): void
|
||||
{
|
||||
$sut = new ExportTemplate();
|
||||
$r = new \ReflectionObject($sut);
|
||||
$p = $r->getProperty('id');
|
||||
$p->setAccessible(true);
|
||||
$p->setValue($sut, 13);
|
||||
self::assertEquals(13, $sut->getId());
|
||||
|
||||
$sut2 = clone $sut;
|
||||
self::assertNull($sut2->getId());
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ class UserTest extends TestCase
|
||||
|
||||
$user->setUserIdentifier('foo');
|
||||
self::assertEquals('foo', $user->getUserIdentifier());
|
||||
self::assertEquals('foo', $user->getIdentifier());
|
||||
self::assertEquals('foo', $user->getDisplayName());
|
||||
$user->setAlias('BAR');
|
||||
self::assertEquals('BAR', $user->getDisplayName());
|
||||
@@ -89,6 +90,7 @@ class UserTest extends TestCase
|
||||
public function testWorkContract(): void
|
||||
{
|
||||
$user = new User();
|
||||
self::assertFalse($user->hasContractSettings());
|
||||
|
||||
self::assertEquals(0, $user->getWorkHoursMonday());
|
||||
self::assertEquals(0, $user->getWorkHoursTuesday());
|
||||
@@ -127,6 +129,7 @@ class UserTest extends TestCase
|
||||
$user->setWorkHoursSunday(7800);
|
||||
$user->setHolidaysPerYear(10.7);
|
||||
self::assertTrue($user->hasWorkHourConfiguration());
|
||||
self::assertTrue($user->hasContractSettings());
|
||||
|
||||
self::assertEquals(7200, $user->getWorkHoursMonday());
|
||||
self::assertEquals(7300, $user->getWorkHoursTuesday());
|
||||
@@ -158,6 +161,16 @@ class UserTest extends TestCase
|
||||
|
||||
$user->setPublicHolidayGroup('DE-NRW');
|
||||
self::assertEquals('DE-NRW', $user->getPublicHolidayGroup());
|
||||
|
||||
self::assertNull($user->getWorkStartingDay());
|
||||
$workStart = new \DateTimeImmutable('2018-07-23');
|
||||
$user->setWorkStartingDay($workStart);
|
||||
self::assertEquals($workStart, $user->getWorkStartingDay());
|
||||
|
||||
self::assertNull($user->getLastWorkingDay());
|
||||
$workStart = new \DateTimeImmutable('2021-02-13');
|
||||
$user->setLastWorkingDay($workStart);
|
||||
self::assertEquals($workStart, $user->getLastWorkingDay());
|
||||
}
|
||||
|
||||
public function testColor(): void
|
||||
@@ -248,6 +261,21 @@ class UserTest extends TestCase
|
||||
|
||||
$user->setPreferenceValue('export_decimal', true);
|
||||
self::assertTrue($user->isExportDecimal());
|
||||
|
||||
$prefs = $user->getPreferences();
|
||||
self::assertCount(3, $prefs);
|
||||
|
||||
self::assertInstanceOf(UserPreference::class, $prefs[0]);
|
||||
self::assertEquals('test', $prefs[0]->getName());
|
||||
|
||||
self::assertInstanceOf(UserPreference::class, $prefs[1]);
|
||||
self::assertEquals('test2', $prefs[1]->getName());
|
||||
|
||||
self::assertInstanceOf(UserPreference::class, $prefs[2]);
|
||||
self::assertEquals('export_decimal', $prefs[2]->getName());
|
||||
|
||||
$user->setPreferences(new ArrayCollection([]));
|
||||
self::assertCount(0, $user->getPreferences());
|
||||
}
|
||||
|
||||
public function testDisplayName(): void
|
||||
@@ -332,6 +360,12 @@ class UserTest extends TestCase
|
||||
self::assertTrue($sut->isTeamleadOf($team2));
|
||||
self::assertTrue($sut->isInTeam($team2));
|
||||
|
||||
self::assertTrue($sut->isTeamleadOf($team2));
|
||||
$user2 = new User();
|
||||
self::assertFalse($sut->isTeamleadOfUser($user2));
|
||||
$team2->addUser($user2);
|
||||
self::assertTrue($sut->isTeamleadOfUser($user2));
|
||||
|
||||
self::assertCount(2, $sut->getTeams());
|
||||
$sut->removeMembership(new TeamMember());
|
||||
self::assertCount(2, $sut->getTeams());
|
||||
@@ -624,4 +658,21 @@ class UserTest extends TestCase
|
||||
self::assertNotNull($user->getSupervisor());
|
||||
self::assertSame($supervisor, $user->getSupervisor());
|
||||
}
|
||||
|
||||
public function testLastLogin(): void
|
||||
{
|
||||
$dateTime = new \DateTime('now', new \DateTimeZone('UTC'));
|
||||
|
||||
$user = new User();
|
||||
$user->setTimezone('Europe/Berlin');
|
||||
|
||||
$lastLogin = $user->getLastLogin();
|
||||
self::assertNull($lastLogin);
|
||||
$user->setLastLogin($dateTime);
|
||||
|
||||
$lastLogin = $user->getLastLogin();
|
||||
self::assertNotNull($lastLogin);
|
||||
self::assertInstanceOf(\DateTime::class, $lastLogin);
|
||||
self::assertEquals('Europe/Berlin', $lastLogin->getTimezone()->getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ use App\Entity\User;
|
||||
use App\Export\Base\CsvRenderer;
|
||||
use App\Export\Base\SpreadsheetRenderer;
|
||||
use App\Tests\Export\Renderer\AbstractRendererTestCase;
|
||||
use App\Tests\Export\Renderer\MetaFieldColumnSubscriber;
|
||||
use App\Tests\Mocks\MetaFieldColumnSubscriberMock;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
@@ -37,12 +38,13 @@ class CsvRendererTest extends AbstractRendererTestCase
|
||||
$security->expects($this->any())->method('getUser')->willReturn($user);
|
||||
$security->expects($this->any())->method('isGranted')->willReturn(true);
|
||||
|
||||
$translator = $this->createMock(TranslatorInterface::class);
|
||||
$translator = $this->getContainer()->get(TranslatorInterface::class);
|
||||
self::assertInstanceOf(TranslatorInterface::class, $translator);
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriberMock());
|
||||
|
||||
return new CsvRenderer(new SpreadsheetRenderer($dispatcher, $security), $translator);
|
||||
return new CsvRenderer(new SpreadsheetRenderer($dispatcher, $security, $this->createMock(LoggerInterface::class)), $translator);
|
||||
}
|
||||
|
||||
public function testConfiguration(): void
|
||||
@@ -50,23 +52,43 @@ class CsvRendererTest extends AbstractRendererTestCase
|
||||
$sut = $this->getAbstractRenderer();
|
||||
|
||||
self::assertEquals('csv', $sut->getId());
|
||||
self::assertEquals('csv', $sut->getTitle());
|
||||
self::assertEquals('default', $sut->getTitle());
|
||||
|
||||
$sut->setTitle('foo-bar');
|
||||
self::assertEquals('foo-bar', $sut->getTitle());
|
||||
|
||||
$sut->setId('bar-id');
|
||||
self::assertEquals('bar-id', $sut->getId());
|
||||
}
|
||||
|
||||
public static function getTestModel(): array
|
||||
{
|
||||
$en = [
|
||||
'Date', 'From', 'To', 'Duration', 'Currency', 'Price', 'Internal price', 'Hourly price', 'Fixed price', 'Name',
|
||||
'User', 'Staff number', 'Customer', 'Project', 'Activity', 'Description', 'Billable', 'Tags',
|
||||
'Type', 'category', 'Account', 'Project number', 'VAT-ID', 'Order number',
|
||||
'Working place', 'Working place', 'Working place', 'Working place', 'Working place', 'Working place', 'mypref',
|
||||
];
|
||||
$de = [
|
||||
'Datum', 'Von', 'Bis', 'Dauer', 'Währung', 'Preis', 'Interner Preis', 'Preis pro Stunde', 'Festpreis', 'Name',
|
||||
'Benutzer', 'Personalnummer', 'Kunde', 'Projekt', 'Tätigkeit', 'Beschreibung', 'Abrechenbar', 'Schlagworte',
|
||||
'Typ', 'category', 'Kundennummer', 'Projektnummer', 'Umsatzsteuer-ID', 'Bestellnummer',
|
||||
'Working place', 'Working place', 'Working place', 'Working place', 'Working place', 'Working place', 'mypref',
|
||||
];
|
||||
|
||||
return [
|
||||
['400', '2437.12', '1947.99', 7, 6, 1, 2, 2, false],
|
||||
['400', '2437.12', '1947.99', 7, 6, 1, 2, 2, true]
|
||||
['400', '2437.12', '1947.99', 7, 6, 1, 2, 2, false, null, $en],
|
||||
['400', '2437.12', '1947.99', 7, 6, 1, 2, 2, true, 'de', $de]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTestModel
|
||||
*/
|
||||
public function testRender(string $totalDuration, string $totalRate, string $expectedRate, int $expectedRows, int $expectedDescriptions, int $expectedUser1, int $expectedUser2, int $expectedUser3, bool $exportDecimal): void
|
||||
public function testRender(string $totalDuration, string $totalRate, string $expectedRate, int $expectedRows, int $expectedDescriptions, int $expectedUser1, int $expectedUser2, int $expectedUser3, bool $exportDecimal, ?string $locale, array $header): void
|
||||
{
|
||||
$sut = $this->getAbstractRenderer($exportDecimal);
|
||||
$sut->setLocale($locale);
|
||||
|
||||
/** @var BinaryFileResponse $response */
|
||||
$response = $this->render($sut);
|
||||
@@ -102,11 +124,13 @@ class CsvRendererTest extends AbstractRendererTestCase
|
||||
$all[] = str_getcsv($row);
|
||||
}
|
||||
|
||||
self::assertEquals($header, $all[0]);
|
||||
|
||||
$expected = [
|
||||
'2019-06-16',
|
||||
'12:00',
|
||||
'12:06',
|
||||
($exportDecimal ? '0.11' : '0:06:40'),
|
||||
($exportDecimal ? '0.11' : '0:06'),
|
||||
//'0.11',
|
||||
'EUR',
|
||||
'0',
|
||||
@@ -134,13 +158,14 @@ class CsvRendererTest extends AbstractRendererTestCase
|
||||
'',
|
||||
'project-foo2',
|
||||
'activity-bar',
|
||||
'',
|
||||
];
|
||||
|
||||
$expected2 = [
|
||||
'2019-06-16',
|
||||
'12:00',
|
||||
'12:06',
|
||||
($exportDecimal ? '0.11' : '0:06:40'),
|
||||
($exportDecimal ? '0.11' : '0:06'),
|
||||
//'0.11',
|
||||
'EUR',
|
||||
'0',
|
||||
@@ -168,6 +193,7 @@ class CsvRendererTest extends AbstractRendererTestCase
|
||||
'',
|
||||
'project-foo2',
|
||||
'activity-bar',
|
||||
'',
|
||||
];
|
||||
|
||||
self::assertEquals(7, \count($all));
|
||||
|
||||
56
tests/Export/Base/PdfRendererTest.php
Normal file
56
tests/Export/Base/PdfRendererTest.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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\Export\Base;
|
||||
|
||||
use App\Export\Base\PDFRenderer;
|
||||
use App\Pdf\HtmlToPdfConverter;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Tests\Export\Renderer\AbstractRendererTestCase;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* @covers \App\Export\Base\PDFRenderer
|
||||
* @covers \App\Export\Base\RendererTrait
|
||||
* @covers \App\Pdf\PdfRendererTrait
|
||||
* @group integration
|
||||
*/
|
||||
class PdfRendererTest extends AbstractRendererTestCase
|
||||
{
|
||||
protected function getAbstractRenderer(bool $exportDecimal = false): PDFRenderer
|
||||
{
|
||||
$twig = $this->createMock(Environment::class);
|
||||
$converter = $this->createMock(HtmlToPdfConverter::class);
|
||||
$projectStatisticService = $this->createMock(ProjectStatisticService::class);
|
||||
|
||||
return new PDFRenderer($twig, $converter, $projectStatisticService);
|
||||
}
|
||||
|
||||
public function testConfiguration(): void
|
||||
{
|
||||
$sut = $this->getAbstractRenderer();
|
||||
|
||||
self::assertEquals('pdf', $sut->getId());
|
||||
self::assertEquals('pdf', $sut->getTitle());
|
||||
|
||||
$sut->setTitle('foo-bar');
|
||||
self::assertEquals('foo-bar', $sut->getTitle());
|
||||
|
||||
$sut->setId('bar-id');
|
||||
self::assertEquals('bar-id', $sut->getId());
|
||||
|
||||
self::assertEquals([], $sut->getPdfOptions());
|
||||
$sut->setPdfOption('foo', 'bar');
|
||||
self::assertEquals(['foo' => 'bar'], $sut->getPdfOptions());
|
||||
$sut->setPdfOption('foo', 'bar2');
|
||||
self::assertEquals(['foo' => 'bar2'], $sut->getPdfOptions());
|
||||
$sut->setPdfOption('hello', 'world');
|
||||
self::assertEquals(['foo' => 'bar2', 'hello' => 'world'], $sut->getPdfOptions());
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,15 @@ namespace App\Tests\Export\Base;
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Export\Base\SpreadsheetRenderer;
|
||||
use App\Export\Package\SpreadsheetPackage;
|
||||
use App\Export\Template;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Tests\Export\Package\MemoryPackage;
|
||||
use App\Tests\Export\Renderer\AbstractRendererTestCase;
|
||||
use App\Tests\Mocks\MetaFieldColumnSubscriberMock;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
@@ -31,7 +36,7 @@ class SpreadsheetRendererTest extends AbstractRendererTestCase
|
||||
$spreadsheetPackage = $this->createMock(SpreadsheetPackage::class);
|
||||
$spreadsheetPackage->expects(self::once())->method('setColumns');
|
||||
|
||||
$renderer = new SpreadsheetRenderer($dispatcher, $security);
|
||||
$renderer = new SpreadsheetRenderer($dispatcher, $security, $this->createMock(LoggerInterface::class));
|
||||
$renderer->writeSpreadsheet($spreadsheetPackage, [], new TimesheetQuery());
|
||||
}
|
||||
|
||||
@@ -60,7 +65,7 @@ class SpreadsheetRendererTest extends AbstractRendererTestCase
|
||||
$exportItem->method('getType')->willReturn('type');
|
||||
$exportItem->method('getCategory')->willReturn('category');
|
||||
|
||||
$renderer = new SpreadsheetRenderer($dispatcher, $security);
|
||||
$renderer = new SpreadsheetRenderer($dispatcher, $security, $this->createMock(LoggerInterface::class));
|
||||
$renderer->writeSpreadsheet($spreadsheetPackage, [$exportItem], new TimesheetQuery());
|
||||
}
|
||||
|
||||
@@ -93,4 +98,96 @@ class SpreadsheetRendererTest extends AbstractRendererTestCase
|
||||
$renderer = new SpreadsheetRenderer($dispatcher, $security);
|
||||
$renderer->writeSpreadsheet($spreadsheetPackage, [$exportItem, $exportItem], new TimesheetQuery());
|
||||
}
|
||||
|
||||
public static function getTestData(): iterable
|
||||
{
|
||||
yield [null, [
|
||||
'date' => 'date',
|
||||
'begin' => 'begin',
|
||||
'end' => 'end',
|
||||
'duration' => 'duration',
|
||||
'currency' => 'currency',
|
||||
'rate' => 'rate',
|
||||
'internalRate' => 'internalRate',
|
||||
'hourlyRate' => 'hourlyRate',
|
||||
'fixedRate' => 'fixedRate',
|
||||
'alias' => 'alias',
|
||||
'username' => 'username',
|
||||
'account_number' => 'account_number',
|
||||
'customer' => 'customer',
|
||||
'project' => 'project',
|
||||
'activity' => 'activity',
|
||||
'description' => 'description',
|
||||
'billable' => 'billable',
|
||||
'tags' => 'tags',
|
||||
'type' => 'type',
|
||||
'category' => 'category',
|
||||
'number' => 'number',
|
||||
'project_number' => 'project_number',
|
||||
'vat_id' => 'vat_id',
|
||||
'orderNumber' => 'orderNumber',
|
||||
'timesheet.meta.foo' => 'Working place',
|
||||
'timesheet.meta.foo2' => 'Working place',
|
||||
'customer.meta.customer-foo' => 'Working place',
|
||||
'project.meta.project-foo' => 'Working place',
|
||||
'project.meta.project-foo2' => 'Working place',
|
||||
'activity.meta.activity-foo' => 'Working place',
|
||||
'user.meta.mypref' => 'mypref',
|
||||
]];
|
||||
|
||||
$template = new Template('test', 'Testing');
|
||||
$template->setLocale('de');
|
||||
$template->setColumns(['date', 'user.name', 'duration_decimal', 'customer.name', 'exported', 'user.meta.mypref']);
|
||||
|
||||
yield [$template, [
|
||||
'date' => 'date',
|
||||
'duration' => 'duration',
|
||||
'username' => 'username',
|
||||
'customer' => 'customer',
|
||||
'exported' => 'exported',
|
||||
'user.meta.mypref' => 'mypref',
|
||||
]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTestData
|
||||
*/
|
||||
public function testWriteSpreadsheetCsv(?Template $template, array $expectedColumns): void
|
||||
{
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriberMock());
|
||||
|
||||
$security = $this->createMock(Security::class);
|
||||
$spreadsheetPackage = new MemoryPackage();
|
||||
|
||||
$exportItem = $this->createMock(ExportableItem::class);
|
||||
$exportItem->method('getBegin')->willReturn(new \DateTime());
|
||||
$exportItem->method('getEnd')->willReturn(new \DateTime());
|
||||
$exportItem->method('getDuration')->willReturn(3600);
|
||||
$exportItem->method('getRate')->willReturn(100.0);
|
||||
$exportItem->method('getInternalRate')->willReturn(80.0);
|
||||
$exportItem->method('getHourlyRate')->willReturn(50.0);
|
||||
$exportItem->method('getFixedRate')->willReturn(200.0);
|
||||
$exportItem->method('getUser')->willReturn(null);
|
||||
$exportItem->method('getProject')->willReturn(null);
|
||||
$exportItem->method('getActivity')->willReturn(null);
|
||||
$exportItem->method('getDescription')->willReturn('Test description');
|
||||
$exportItem->method('isBillable')->willReturn(true);
|
||||
$exportItem->method('getTagsAsArray')->willReturn(['tag1', 'tag2']);
|
||||
$exportItem->method('getType')->willReturn('type');
|
||||
$exportItem->method('getCategory')->willReturn('category');
|
||||
|
||||
$renderer = new SpreadsheetRenderer($dispatcher, $security, $this->createMock(LoggerInterface::class));
|
||||
$renderer->setTemplate($template);
|
||||
$renderer->writeSpreadsheet($spreadsheetPackage, [$exportItem], new TimesheetQuery());
|
||||
|
||||
$columnNames = [];
|
||||
foreach ($spreadsheetPackage->getColumns() as $column) {
|
||||
$columnNames[$column->getName()] = $column->getHeader();
|
||||
}
|
||||
|
||||
self::assertEquals(null, $spreadsheetPackage->getFilename());
|
||||
self::assertEquals($expectedColumns, $columnNames);
|
||||
self::assertCount(2, $spreadsheetPackage->getRows());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Entity\User;
|
||||
use App\Export\Base\SpreadsheetRenderer;
|
||||
use App\Export\Base\XlsxRenderer;
|
||||
use App\Tests\Export\Renderer\AbstractRendererTestCase;
|
||||
use App\Tests\Export\Renderer\MetaFieldColumnSubscriber;
|
||||
use App\Tests\Mocks\MetaFieldColumnSubscriberMock;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
@@ -38,7 +38,7 @@ class XlsxRendererTest extends AbstractRendererTestCase
|
||||
$translator->method('trans')->willReturnArgument(0);
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber());
|
||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriberMock());
|
||||
|
||||
return new XlsxRenderer(new SpreadsheetRenderer($dispatcher, $security), $translator);
|
||||
}
|
||||
@@ -46,9 +46,16 @@ class XlsxRendererTest extends AbstractRendererTestCase
|
||||
public function testConfiguration(): void
|
||||
{
|
||||
$sut = $this->getAbstractRenderer();
|
||||
$sut->setLocale('de');
|
||||
|
||||
self::assertEquals('xlsx', $sut->getId());
|
||||
self::assertEquals('xlsx', $sut->getTitle());
|
||||
self::assertEquals('default', $sut->getTitle());
|
||||
|
||||
$sut->setTitle('foo-bar');
|
||||
self::assertEquals('foo-bar', $sut->getTitle());
|
||||
|
||||
$sut->setId('bar-id');
|
||||
self::assertEquals('bar-id', $sut->getId());
|
||||
}
|
||||
|
||||
public function testRender(): void
|
||||
|
||||
@@ -19,13 +19,13 @@ class DurationFormatterTest extends TestCase
|
||||
{
|
||||
public function testGetFormat(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$formatter = new DurationFormatter('[hh]:mm:ss');
|
||||
self::assertEquals('[hh]:mm:ss', $formatter->getFormat());
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationQuiteLong(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$formatter = new DurationFormatter('[hh]:mm:ss');
|
||||
$result = $formatter->formatValue(701213);
|
||||
self::assertInstanceOf(\DateInterval::class, $result);
|
||||
self::assertEquals('194:46:53', $result->format('%r%H:%I:%S'));
|
||||
@@ -33,7 +33,7 @@ class DurationFormatterTest extends TestCase
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForNumericValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$formatter = new DurationFormatter('[hh]:mm:ss');
|
||||
$result = $formatter->formatValue(7213);
|
||||
self::assertInstanceOf(\DateInterval::class, $result);
|
||||
self::assertEquals('02:00:13', $result->format('%r%H:%I:%S'));
|
||||
@@ -41,7 +41,7 @@ class DurationFormatterTest extends TestCase
|
||||
|
||||
public function testFormatValueReturnsZeroForNonNumericValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$formatter = new DurationFormatter('[hh]:mm:ss');
|
||||
$result = $formatter->formatValue('not a number');
|
||||
self::assertInstanceOf(\DateInterval::class, $result);
|
||||
self::assertEquals('00:00:00', $result->format('%r%H:%I:%S'));
|
||||
@@ -49,7 +49,7 @@ class DurationFormatterTest extends TestCase
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForFloatValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$formatter = new DurationFormatter('[hh]:mm:ss');
|
||||
$result = $formatter->formatValue(4521.5);
|
||||
self::assertInstanceOf(\DateInterval::class, $result);
|
||||
self::assertEquals('01:15:21', $result->format('%r%H:%I:%S'));
|
||||
@@ -57,7 +57,7 @@ class DurationFormatterTest extends TestCase
|
||||
|
||||
public function testFormatValueReturnsZeroForNullValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$formatter = new DurationFormatter('[hh]:mm:ss');
|
||||
$result = $formatter->formatValue(null);
|
||||
self::assertInstanceOf(\DateInterval::class, $result);
|
||||
self::assertEquals('00:00:00', $result->format('%r%H:%I:%S'));
|
||||
@@ -65,7 +65,7 @@ class DurationFormatterTest extends TestCase
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForNegativeValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$formatter = new DurationFormatter('[hh]:mm:ss');
|
||||
$result = $formatter->formatValue(-3600);
|
||||
self::assertInstanceOf(\DateInterval::class, $result);
|
||||
self::assertEquals('-01:00:00', $result->format('%r%H:%I:%S'));
|
||||
|
||||
@@ -19,42 +19,50 @@ class DurationPlainFormatterTest extends TestCase
|
||||
{
|
||||
public function testFormatValueReturnsFormattedDurationQuiteLong(): void
|
||||
{
|
||||
$formatter = new DurationPlainFormatter();
|
||||
$formatter = new DurationPlainFormatter(true);
|
||||
$result = $formatter->formatValue(701213);
|
||||
self::assertEquals('194:46:53', $result);
|
||||
|
||||
$formatter = new DurationPlainFormatter(false);
|
||||
$result = $formatter->formatValue(701213);
|
||||
self::assertEquals('194:46', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForNumericValue(): void
|
||||
{
|
||||
$formatter = new DurationPlainFormatter();
|
||||
$formatter = new DurationPlainFormatter(true);
|
||||
$result = $formatter->formatValue(8246);
|
||||
self::assertEquals('2:17:26', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsZeroForNonNumericValue(): void
|
||||
{
|
||||
$formatter = new DurationPlainFormatter();
|
||||
$formatter = new DurationPlainFormatter(true);
|
||||
$result = $formatter->formatValue('not a number');
|
||||
self::assertEquals('0:00:00', $result);
|
||||
|
||||
$formatter = new DurationPlainFormatter(false);
|
||||
$result = $formatter->formatValue('not a number');
|
||||
self::assertEquals('0:00', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForFloatValue(): void
|
||||
{
|
||||
$formatter = new DurationPlainFormatter();
|
||||
$formatter = new DurationPlainFormatter(true);
|
||||
$result = $formatter->formatValue(44513.5);
|
||||
self::assertEquals('12:21:53', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsZeroForNullValue(): void
|
||||
{
|
||||
$formatter = new DurationPlainFormatter();
|
||||
$formatter = new DurationPlainFormatter(true);
|
||||
$result = $formatter->formatValue(null);
|
||||
self::assertEquals('0:00:00', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForNegativeValue(): void
|
||||
{
|
||||
$formatter = new DurationPlainFormatter();
|
||||
$formatter = new DurationPlainFormatter(true);
|
||||
$result = $formatter->formatValue(-3600);
|
||||
self::assertEquals('-1:00:00', $result);
|
||||
}
|
||||
|
||||
71
tests/Export/Package/MemoryPackage.php
Normal file
71
tests/Export/Package/MemoryPackage.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?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\Export\Package;
|
||||
|
||||
use App\Export\Package\Column;
|
||||
use App\Export\Package\SpreadsheetPackage;
|
||||
|
||||
/**
|
||||
* @covers \App\Export\Package\SpoutSpreadsheet
|
||||
*/
|
||||
class MemoryPackage implements SpreadsheetPackage
|
||||
{
|
||||
private ?string $filename = null;
|
||||
/** @var array<Column> */
|
||||
private array $columns = [];
|
||||
private array $rows = [];
|
||||
private bool $saved = false;
|
||||
|
||||
public function open(string $filename): void
|
||||
{
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->saved = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Column> $columns
|
||||
*/
|
||||
public function setColumns(array $columns): void
|
||||
{
|
||||
$this->columns = $columns;
|
||||
}
|
||||
|
||||
public function addRow(array $columns, array $options = []): void
|
||||
{
|
||||
$this->rows[] = ['columns' => $columns, 'options' => $options];
|
||||
}
|
||||
|
||||
public function getFilename(): ?string
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Column[]
|
||||
*/
|
||||
public function getColumns(): array
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
public function getRows(): array
|
||||
{
|
||||
return $this->rows;
|
||||
}
|
||||
|
||||
public function isSaved(): bool
|
||||
{
|
||||
return $this->saved;
|
||||
}
|
||||
}
|
||||
@@ -13,22 +13,15 @@ use App\Entity\Activity;
|
||||
use App\Entity\ActivityMeta;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\CustomerMeta;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\ProjectMeta;
|
||||
use App\Entity\Tag;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\TimesheetMeta;
|
||||
use App\Entity\User;
|
||||
use App\Event\ActivityMetaDisplayEvent;
|
||||
use App\Event\CustomerMetaDisplayEvent;
|
||||
use App\Event\ProjectMetaDisplayEvent;
|
||||
use App\Event\TimesheetMetaDisplayEvent;
|
||||
use App\Export\ExportRendererInterface;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
abstract class AbstractRendererTestCase extends KernelTestCase
|
||||
@@ -145,47 +138,3 @@ abstract class AbstractRendererTestCase extends KernelTestCase
|
||||
return $renderer->render($entries, $query);
|
||||
}
|
||||
}
|
||||
|
||||
class MetaFieldColumnSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
TimesheetMetaDisplayEvent::class => ['loadTimesheetField', 200],
|
||||
CustomerMetaDisplayEvent::class => ['loadCustomerField', 200],
|
||||
ProjectMetaDisplayEvent::class => ['loadProjectField', 200],
|
||||
ActivityMetaDisplayEvent::class => ['loadActivityField', 200],
|
||||
];
|
||||
}
|
||||
|
||||
public function loadTimesheetField(TimesheetMetaDisplayEvent $event): void
|
||||
{
|
||||
$event->addField($this->prepareEntity(new TimesheetMeta(), 'foo'));
|
||||
$event->addField($this->prepareEntity(new TimesheetMeta(), 'foo2'));
|
||||
}
|
||||
|
||||
public function loadCustomerField(CustomerMetaDisplayEvent $event): void
|
||||
{
|
||||
$event->addField($this->prepareEntity(new CustomerMeta(), 'customer-foo'));
|
||||
}
|
||||
|
||||
public function loadProjectField(ProjectMetaDisplayEvent $event): void
|
||||
{
|
||||
$event->addField($this->prepareEntity(new ProjectMeta(), 'project-foo'));
|
||||
$event->addField($this->prepareEntity(new ProjectMeta(), 'project-foo2')->setIsVisible(false));
|
||||
}
|
||||
|
||||
public function loadActivityField(ActivityMetaDisplayEvent $event): void
|
||||
{
|
||||
$event->addField($this->prepareEntity(new ActivityMeta(), 'activity-foo'));
|
||||
}
|
||||
|
||||
private function prepareEntity(MetaTableTypeInterface $meta, string $name): MetaTableTypeInterface
|
||||
{
|
||||
return $meta
|
||||
->setLabel('Working place')
|
||||
->setName($name)
|
||||
->setType(TextType::class)
|
||||
->setIsVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
44
tests/Export/Renderer/CsvRendererFactoryTest.php
Normal file
44
tests/Export/Renderer/CsvRendererFactoryTest.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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\Export\Renderer;
|
||||
|
||||
use App\Export\Base\CsvRenderer;
|
||||
use App\Export\Renderer\CsvRendererFactory;
|
||||
use App\Export\Template;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\Export\Renderer\CsvRendererFactory
|
||||
*/
|
||||
class CsvRendererFactoryTest extends TestCase
|
||||
{
|
||||
public function testCreate(): void
|
||||
{
|
||||
$sut = new CsvRendererFactory(
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(Security::class),
|
||||
$this->createMock(TranslatorInterface::class),
|
||||
$this->createMock(LoggerInterface::class)
|
||||
);
|
||||
|
||||
$template = new Template('foo-id', 'bar-title');
|
||||
$template->setLocale('it_IT');
|
||||
|
||||
$renderer = $sut->create($template);
|
||||
|
||||
self::assertInstanceOf(CsvRenderer::class, $renderer);
|
||||
self::assertEquals('foo-id', $renderer->getId());
|
||||
self::assertEquals('bar-title', $renderer->getTitle());
|
||||
}
|
||||
}
|
||||
44
tests/Export/Renderer/XlsxRendererFactoryTest.php
Normal file
44
tests/Export/Renderer/XlsxRendererFactoryTest.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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\Export\Renderer;
|
||||
|
||||
use App\Export\Base\XlsxRenderer;
|
||||
use App\Export\Renderer\XlsxRendererFactory;
|
||||
use App\Export\Template;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\Export\Renderer\XlsxRendererFactory
|
||||
*/
|
||||
class XlsxRendererFactoryTest extends TestCase
|
||||
{
|
||||
public function testCreate(): void
|
||||
{
|
||||
$sut = new XlsxRendererFactory(
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(Security::class),
|
||||
$this->createMock(TranslatorInterface::class),
|
||||
$this->createMock(LoggerInterface::class)
|
||||
);
|
||||
|
||||
$template = new Template('foo-id', 'bar-title');
|
||||
$template->setLocale('it_IT');
|
||||
|
||||
$renderer = $sut->create($template);
|
||||
|
||||
self::assertInstanceOf(XlsxRenderer::class, $renderer);
|
||||
self::assertEquals('foo-id', $renderer->getId());
|
||||
self::assertEquals('bar-title', $renderer->getTitle());
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,22 @@
|
||||
namespace App\Tests\Export;
|
||||
|
||||
use App\Activity\ActivityStatisticService;
|
||||
use App\Entity\ExportTemplate;
|
||||
use App\Export\Base\CsvRenderer;
|
||||
use App\Export\Base\HtmlRenderer;
|
||||
use App\Export\Base\XlsxRenderer;
|
||||
use App\Export\ExportRepositoryInterface;
|
||||
use App\Export\ServiceExport;
|
||||
use App\Export\Timesheet\HtmlRenderer as HtmlExporter;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Repository\ExportTemplateRepository;
|
||||
use App\Repository\Query\ExportQuery;
|
||||
use App\Tests\Mocks\Export\CsvRendererFactoryMock;
|
||||
use App\Tests\Mocks\Export\HtmlRendererFactoryMock;
|
||||
use App\Tests\Mocks\Export\PdfRendererFactoryMock;
|
||||
use App\Tests\Mocks\Export\XlsxRendererFactoryMock;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Twig\Environment;
|
||||
@@ -28,12 +35,48 @@ use Twig\Environment;
|
||||
*/
|
||||
class ServiceExportTest extends TestCase
|
||||
{
|
||||
private function createSut(): ServiceExport
|
||||
private function createSut(bool $withTemplates = false, int $failureCount = 1): ServiceExport
|
||||
{
|
||||
$repository = $this->createMock(ExportTemplateRepository::class);
|
||||
$templates = [];
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
if ($withTemplates) {
|
||||
$template1 = $this->createMock(ExportTemplate::class);
|
||||
$template1->method('getId')->willReturn(1);
|
||||
$template1->method('getTitle')->willReturn('CSV Test');
|
||||
$template1->method('getLanguage')->willReturn('de');
|
||||
$template1->method('getRenderer')->willReturn('csv');
|
||||
$template1->method('getColumns')->willReturn(['date', 'customer.name', 'duration', 'rate']);
|
||||
|
||||
$template2 = $this->createMock(ExportTemplate::class);
|
||||
$template2->method('getId')->willReturn(2);
|
||||
$template2->method('getTitle')->willReturn('XLSX Test');
|
||||
$template2->method('getLanguage')->willReturn('it');
|
||||
$template2->method('getRenderer')->willReturn('xlsx');
|
||||
$template2->method('getColumns')->willReturn(['date', 'begin', 'duration', 'rate', 'user.name']);
|
||||
|
||||
$template3 = $this->createMock(ExportTemplate::class);
|
||||
$template3->method('getTitle')->willReturn('XLSX Test');
|
||||
$template3->method('getLanguage')->willReturn('it');
|
||||
$template3->method('getRenderer')->willReturn('foo'); // invalid renderer will be ignored
|
||||
$template3->method('getColumns')->willReturn(['date', 'begin', 'duration', 'rate', 'user.name']);
|
||||
|
||||
$logger->expects($this->exactly($failureCount))->method('error')->with('Unknown export template type: ' . $template3->getRenderer());
|
||||
|
||||
$templates = [$template1, $template2, $template3];
|
||||
}
|
||||
|
||||
$repository->method('findAll')->willReturn($templates);
|
||||
|
||||
return new ServiceExport(
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
(new HtmlRendererFactoryMock($this))->create(),
|
||||
(new PdfRendererFactoryMock($this))->create(),
|
||||
(new CsvRendererFactoryMock($this))->create(),
|
||||
(new XlsxRendererFactoryMock($this))->create(),
|
||||
$repository,
|
||||
$logger,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,4 +131,18 @@ class ServiceExportTest extends TestCase
|
||||
|
||||
self::assertEquals([], $items);
|
||||
}
|
||||
|
||||
public function testWithTemplates(): void
|
||||
{
|
||||
$sut = $this->createSut(true, 5);
|
||||
|
||||
$renderer = $sut->getRenderer();
|
||||
self::assertCount(2, $renderer);
|
||||
self::assertInstanceOf(CsvRenderer::class, $renderer[0]);
|
||||
self::assertInstanceOf(XlsxRenderer::class, $renderer[1]);
|
||||
self::assertInstanceOf(CsvRenderer::class, $sut->getRendererById('1'));
|
||||
self::assertNull($sut->getRendererById('default'));
|
||||
self::assertNull($sut->getRendererById('csv'));
|
||||
self::assertNull($sut->getRendererById('xlsx'));
|
||||
}
|
||||
}
|
||||
|
||||
63
tests/Export/TemplateTest.php
Normal file
63
tests/Export/TemplateTest.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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\Export;
|
||||
|
||||
use App\Export\Template;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Export\Template
|
||||
*/
|
||||
class TemplateTest extends TestCase
|
||||
{
|
||||
public function testDefaultValues(): void
|
||||
{
|
||||
$template = new Template('id', 'title');
|
||||
self::assertEquals('id', $template->getId());
|
||||
self::assertEquals('title', $template->getTitle());
|
||||
self::assertNull($template->getLocale());
|
||||
self::assertEquals([], $template->getColumns());
|
||||
self::assertEquals([], $template->getOptions());
|
||||
}
|
||||
|
||||
public function testSetsAndGetsColumnsCorrectly(): void
|
||||
{
|
||||
$template = new Template('id', 'title');
|
||||
$columns = ['Column1', 'Column2'];
|
||||
$template->setColumns($columns);
|
||||
self::assertEquals($columns, $template->getColumns());
|
||||
$template->setColumns([]);
|
||||
self::assertEquals([], $template->getColumns());
|
||||
}
|
||||
|
||||
public function testSetsAndGetsOptionsCorrectly(): void
|
||||
{
|
||||
$template = new Template('id', 'title');
|
||||
$options = ['key1' => 'value1', 'key2' => 'value2'];
|
||||
$template->setOptions($options);
|
||||
self::assertEquals($options, $template->getOptions());
|
||||
$template->setOptions([]);
|
||||
self::assertEquals([], $template->getOptions());
|
||||
}
|
||||
|
||||
public function testSetsAndGetsLocaleCorrectly(): void
|
||||
{
|
||||
$template = new Template('id', 'title');
|
||||
$template->setLocale('en_US');
|
||||
self::assertEquals('en_US', $template->getLocale());
|
||||
}
|
||||
|
||||
public function testHandlesNullLocaleGracefully(): void
|
||||
{
|
||||
$template = new Template('id', 'title');
|
||||
$template->setLocale(null);
|
||||
self::assertNull($template->getLocale());
|
||||
}
|
||||
}
|
||||
61
tests/Form/ExportTemplateSpreadsheetFormTest.php
Normal file
61
tests/Form/ExportTemplateSpreadsheetFormTest.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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\Form;
|
||||
|
||||
use App\Configuration\LocaleService;
|
||||
use App\Entity\ExportTemplate;
|
||||
use App\Form\ExportTemplateSpreadsheetForm;
|
||||
use App\Form\Type\ExportColumnsType;
|
||||
use App\Form\Type\LanguageType;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormTypeInterface;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\Form\ExportTemplateSpreadsheetForm
|
||||
*/
|
||||
class ExportTemplateSpreadsheetFormTest extends TypeTestCase
|
||||
{
|
||||
/**
|
||||
* @return FormTypeInterface[]
|
||||
*/
|
||||
protected function getTypes(): array // @phpstan-ignore missingType.generics
|
||||
{
|
||||
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$translator = $this->createMock(TranslatorInterface::class);
|
||||
|
||||
return [
|
||||
new ExportColumnsType($dispatcher, $translator),
|
||||
new LanguageType(new LocaleService([]))
|
||||
];
|
||||
}
|
||||
|
||||
public function testWithGlobalNewActivity(): void
|
||||
{
|
||||
$model = new ExportTemplate();
|
||||
$form = $this->factory->createBuilder(ExportTemplateSpreadsheetForm::class, $model);
|
||||
|
||||
$attr = $form->getFormConfig()->getOption('attr');
|
||||
self::assertIsArray($attr);
|
||||
self::assertArrayHasKey('data-form-event', $attr);
|
||||
self::assertEquals('kimai.exportTemplate', $attr['data-form-event']);
|
||||
|
||||
self::assertTrue($form->has('title'));
|
||||
self::assertTrue($form->has('renderer'));
|
||||
self::assertTrue($form->has('language'));
|
||||
self::assertTrue($form->has('columns'));
|
||||
|
||||
self::assertTrue($form->get('title')->getRequired());
|
||||
self::assertTrue($form->get('renderer')->getRequired());
|
||||
self::assertFalse($form->get('language')->getRequired());
|
||||
self::assertTrue($form->get('columns')->getRequired());
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ namespace App\Tests\Form\Extension;
|
||||
use App\Form\Extension\DocumentationLinkExtension;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
@@ -43,4 +45,18 @@ class DocumentationLinkExtensionTest extends TestCase
|
||||
$this->expectException(InvalidOptionsException::class);
|
||||
$resolver->resolve(['docu_chapter' => true]);
|
||||
}
|
||||
|
||||
public function testBuildView(): void
|
||||
{
|
||||
$sut = new DocumentationLinkExtension();
|
||||
$form = $this->createMock(FormInterface::class);
|
||||
|
||||
$view = new FormView();
|
||||
$sut->buildView($view, $form, ['docu_chapter' => null]);
|
||||
self::assertEquals(['attr' => [], 'value' => null, 'docu_chapter' => null], $view->vars);
|
||||
|
||||
$view = new FormView();
|
||||
$sut->buildView($view, $form, ['docu_chapter' => 'customers']);
|
||||
self::assertEquals(['attr' => [], 'value' => null, 'docu_chapter' => 'customers'], $view->vars);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ use App\Form\Extension\EnhancedChoiceTypeExtension;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
@@ -30,15 +32,52 @@ class EnhancedChoiceTypeExtensionTest extends TestCase
|
||||
$resolver = new OptionsResolver();
|
||||
$sut = new EnhancedChoiceTypeExtension();
|
||||
$sut->configureOptions($resolver);
|
||||
self::assertEquals(['selectpicker', 'width', 'search'], $resolver->getDefinedOptions());
|
||||
self::assertEquals(['selectpicker', 'width', 'search', 'order'], $resolver->getDefinedOptions());
|
||||
self::assertTrue($resolver->hasDefault('selectpicker'));
|
||||
self::assertTrue($resolver->hasDefault('width'));
|
||||
self::assertTrue($resolver->hasDefault('search'));
|
||||
self::assertTrue($resolver->hasDefault('order'));
|
||||
self::assertFalse($resolver->isRequired('selectpicker'));
|
||||
self::assertFalse($resolver->isRequired('width'));
|
||||
self::assertFalse($resolver->isRequired('search'));
|
||||
|
||||
$result = $resolver->resolve([]);
|
||||
self::assertEquals(['selectpicker' => true, 'width' => '100%', 'search' => true], $result);
|
||||
self::assertEquals(['selectpicker' => true, 'width' => '100%', 'search' => true, 'order' => false], $result);
|
||||
}
|
||||
|
||||
public static function getTestData(): iterable
|
||||
{
|
||||
yield [
|
||||
['expanded' => true],
|
||||
['value' => null, 'attr' => []]
|
||||
];
|
||||
|
||||
yield [
|
||||
['multiple' => false, 'width' => false, 'search' => true, 'selectpicker' => true, 'order', 'required' => false],
|
||||
['value' => null, 'attr' => ['class' => 'selectpicker']]
|
||||
];
|
||||
|
||||
yield [
|
||||
['multiple' => false, 'width' => '100%', 'search' => true, 'required' => false, 'order' => true],
|
||||
['value' => null, 'attr' => ['class' => 'selectpicker', 'data-width' => '100%', 'data-order' => 1]]
|
||||
];
|
||||
|
||||
yield [
|
||||
['multiple' => true, 'width' => '50%', 'search' => false, 'required' => true, 'attr' => []],
|
||||
['value' => null, 'attr' => ['size' => 1, 'class' => 'selectpicker', 'data-width' => '50%', 'data-disable-search' => 1, 'required' => 'required', 'placeholder' => '']]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTestData
|
||||
*/
|
||||
public function testBuildView(array $options, array $expected): void
|
||||
{
|
||||
$sut = new EnhancedChoiceTypeExtension();
|
||||
$view = new FormView();
|
||||
$form = $this->createMock(FormInterface::class);
|
||||
|
||||
$sut->buildView($view, $form, $options);
|
||||
self::assertEquals($expected, $view->vars);
|
||||
}
|
||||
}
|
||||
|
||||
72
tests/Form/Type/ExportColumnsTypeTest.php
Normal file
72
tests/Form/Type/ExportColumnsTypeTest.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?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\Form\Type;
|
||||
|
||||
use App\Form\Type\ExportColumnsType;
|
||||
use App\Tests\Mocks\MetaFieldColumnSubscriberMock;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\Form\Type\ExportColumnsType
|
||||
*/
|
||||
class ExportColumnsTypeTest extends TypeTestCase
|
||||
{
|
||||
public static function getTestData(): iterable
|
||||
{
|
||||
yield [['foo', 'bar'], []];
|
||||
|
||||
yield [
|
||||
['user.name', 'customer.meta.customer-foo', 'duration', 'hello', 'user.meta.mypref'],
|
||||
['user.name', 'customer.meta.customer-foo', 'duration', 'user.meta.mypref']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ExportColumnsType[]
|
||||
*/
|
||||
protected function getTypes(): array
|
||||
{
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->addSubscriber(new MetaFieldColumnSubscriberMock());
|
||||
|
||||
$translator = $this->createMock(TranslatorInterface::class);
|
||||
|
||||
return [
|
||||
new ExportColumnsType($dispatcher, $translator)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $value
|
||||
* @param array<mixed> $expected
|
||||
* @dataProvider getTestData
|
||||
*/
|
||||
public function testSubmitValidData(array $value, array $expected): void
|
||||
{
|
||||
$data = ['columns' => $value];
|
||||
$model = new TypeTestModel(['columns' => []]);
|
||||
|
||||
$form = $this->factory->createBuilder(FormType::class, $model);
|
||||
$form->add('columns', ExportColumnsType::class);
|
||||
$form = $form->getForm();
|
||||
|
||||
$expected = new TypeTestModel([
|
||||
'columns' => $expected
|
||||
]);
|
||||
|
||||
$form->submit($data);
|
||||
|
||||
self::assertTrue($form->isSynchronized());
|
||||
self::assertEquals($expected, $model);
|
||||
}
|
||||
}
|
||||
51
tests/Form/Type/ExportRendererTypeTest.php
Normal file
51
tests/Form/Type/ExportRendererTypeTest.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Form\Type;
|
||||
|
||||
use App\Form\Type\ExportRendererType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Form\Type\ExportRendererType
|
||||
*/
|
||||
class ExportRendererTypeTest extends TypeTestCase
|
||||
{
|
||||
public static function getTestData(): iterable
|
||||
{
|
||||
yield ['foo', null];
|
||||
yield ['csv', 'csv'];
|
||||
yield ['csV', null];
|
||||
yield ['xlsx', 'xlsx'];
|
||||
yield ['XLSX', null];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTestData
|
||||
*/
|
||||
public function testSubmitValidData(string $value, string|null $expected): void
|
||||
{
|
||||
$data = ['renderer' => $value];
|
||||
$model = new TypeTestModel(['renderer' => null]);
|
||||
|
||||
$form = $this->factory->createBuilder(FormType::class, $model);
|
||||
$form->add('renderer', ExportRendererType::class);
|
||||
$form = $form->getForm();
|
||||
|
||||
$expected = new TypeTestModel([
|
||||
'renderer' => $expected
|
||||
]);
|
||||
|
||||
$form->submit($data);
|
||||
|
||||
self::assertTrue($form->isSynchronized());
|
||||
self::assertEquals($expected, $model);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace App\Tests\Form\Type;
|
||||
*/
|
||||
class TypeTestModel extends \ArrayObject
|
||||
{
|
||||
public function __set(string $name, string|int|null $value)
|
||||
public function __set(string $name, string|int|null|array $value)
|
||||
{
|
||||
$this->offsetSet($name, $value);
|
||||
}
|
||||
|
||||
30
tests/Mocks/Export/CsvRendererFactoryMock.php
Normal file
30
tests/Mocks/Export/CsvRendererFactoryMock.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Mocks\Export;
|
||||
|
||||
use App\Export\Renderer\CsvRendererFactory;
|
||||
use App\Tests\Mocks\AbstractMockFactory;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class CsvRendererFactoryMock extends AbstractMockFactory
|
||||
{
|
||||
public function create(): CsvRendererFactory
|
||||
{
|
||||
return new CsvRendererFactory(
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(Security::class),
|
||||
$this->createMock(TranslatorInterface::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
);
|
||||
}
|
||||
}
|
||||
30
tests/Mocks/Export/XlsxRendererFactoryMock.php
Normal file
30
tests/Mocks/Export/XlsxRendererFactoryMock.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Mocks\Export;
|
||||
|
||||
use App\Export\Renderer\XlsxRendererFactory;
|
||||
use App\Tests\Mocks\AbstractMockFactory;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class XlsxRendererFactoryMock extends AbstractMockFactory
|
||||
{
|
||||
public function create(): XlsxRendererFactory
|
||||
{
|
||||
return new XlsxRendererFactory(
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(Security::class),
|
||||
$this->createMock(TranslatorInterface::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
);
|
||||
}
|
||||
}
|
||||
74
tests/Mocks/MetaFieldColumnSubscriberMock.php
Normal file
74
tests/Mocks/MetaFieldColumnSubscriberMock.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?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\Mocks;
|
||||
|
||||
use App\Entity\ActivityMeta;
|
||||
use App\Entity\CustomerMeta;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\ProjectMeta;
|
||||
use App\Entity\TimesheetMeta;
|
||||
use App\Entity\UserPreference;
|
||||
use App\Event\ActivityMetaDisplayEvent;
|
||||
use App\Event\CustomerMetaDisplayEvent;
|
||||
use App\Event\ProjectMetaDisplayEvent;
|
||||
use App\Event\TimesheetMetaDisplayEvent;
|
||||
use App\Event\UserPreferenceDisplayEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
|
||||
class MetaFieldColumnSubscriberMock implements EventSubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
TimesheetMetaDisplayEvent::class => ['loadTimesheetField', 200],
|
||||
CustomerMetaDisplayEvent::class => ['loadCustomerField', 200],
|
||||
ProjectMetaDisplayEvent::class => ['loadProjectField', 200],
|
||||
ActivityMetaDisplayEvent::class => ['loadActivityField', 200],
|
||||
UserPreferenceDisplayEvent::class => ['loadUserField', 200],
|
||||
];
|
||||
}
|
||||
|
||||
public function loadTimesheetField(TimesheetMetaDisplayEvent $event): void
|
||||
{
|
||||
$event->addField($this->prepareEntity(new TimesheetMeta(), 'foo'));
|
||||
$event->addField($this->prepareEntity(new TimesheetMeta(), 'foo2'));
|
||||
}
|
||||
|
||||
public function loadCustomerField(CustomerMetaDisplayEvent $event): void
|
||||
{
|
||||
$event->addField($this->prepareEntity(new CustomerMeta(), 'customer-foo'));
|
||||
}
|
||||
|
||||
public function loadProjectField(ProjectMetaDisplayEvent $event): void
|
||||
{
|
||||
$event->addField($this->prepareEntity(new ProjectMeta(), 'project-foo'));
|
||||
$event->addField($this->prepareEntity(new ProjectMeta(), 'project-foo2')->setIsVisible(false));
|
||||
}
|
||||
|
||||
public function loadActivityField(ActivityMetaDisplayEvent $event): void
|
||||
{
|
||||
$event->addField($this->prepareEntity(new ActivityMeta(), 'activity-foo'));
|
||||
}
|
||||
|
||||
public function loadUserField(UserPreferenceDisplayEvent $event): void
|
||||
{
|
||||
$event->addPreference(new UserPreference('mypref', 'hello world'));
|
||||
}
|
||||
|
||||
private function prepareEntity(MetaTableTypeInterface $meta, string $name): MetaTableTypeInterface
|
||||
{
|
||||
return $meta
|
||||
->setLabel('Working place')
|
||||
->setName($name)
|
||||
->setType(TextType::class)
|
||||
->setIsVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class ColorChoicesValidatorTest extends ConstraintValidatorTestCase
|
||||
return new ColorChoicesValidator();
|
||||
}
|
||||
|
||||
public static function getValidColors()
|
||||
public static function getValidColors(): iterable
|
||||
{
|
||||
yield ['#000000'];
|
||||
yield ['#fff000'];
|
||||
@@ -57,7 +57,7 @@ class ColorChoicesValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public static function getInvalidColors()
|
||||
public static function getInvalidColors(): iterable
|
||||
{
|
||||
yield ['sdf_sdf|#000000', null, 'sdf_sdf', '#000000'];
|
||||
yield ['sdfghjklöß.|#aaabbb', null, 'sdfghjklöß.', '#aaabbb'];
|
||||
|
||||
@@ -27,7 +27,7 @@ class DateTimeFormatValidatorTest extends ConstraintValidatorTestCase
|
||||
return new DateTimeFormatValidator();
|
||||
}
|
||||
|
||||
public static function getValidData()
|
||||
public static function getValidData(): array
|
||||
{
|
||||
return [
|
||||
['10:00'],
|
||||
@@ -56,7 +56,7 @@ class DateTimeFormatValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public static function getInvalidData()
|
||||
public static function getInvalidData(): array
|
||||
{
|
||||
return [
|
||||
['13-13'],
|
||||
|
||||
92
tests/Validator/Constraints/ExportRendererValidatorTest.php
Normal file
92
tests/Validator/Constraints/ExportRendererValidatorTest.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use App\Validator\Constraints\ExportRenderer;
|
||||
use App\Validator\Constraints\ExportRendererValidator;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Validator\Constraints\ExportRenderer
|
||||
* @covers \App\Validator\Constraints\ExportRendererValidator
|
||||
* @extends ConstraintValidatorTestCase<ExportRendererValidator>
|
||||
*/
|
||||
class ExportRendererValidatorTest extends ConstraintValidatorTestCase
|
||||
{
|
||||
protected function createValidator(): ExportRendererValidator
|
||||
{
|
||||
return new ExportRendererValidator();
|
||||
}
|
||||
|
||||
public static function getValidColors(): iterable
|
||||
{
|
||||
yield ['csv'];
|
||||
yield ['xlsx'];
|
||||
yield [null];
|
||||
}
|
||||
|
||||
public function testConstraintIsInvalid(): void
|
||||
{
|
||||
$this->expectException(UnexpectedTypeException::class);
|
||||
|
||||
$this->validator->validate('#000', new NotBlank());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getValidColors
|
||||
*/
|
||||
public function testConstraintWithValidColor(?string $color): void
|
||||
{
|
||||
$constraint = new ExportRenderer();
|
||||
$this->validator->validate($color, $constraint);
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public static function getInvalidColors(): iterable
|
||||
{
|
||||
yield ['CSV'];
|
||||
yield ['XLSX'];
|
||||
yield ['PDF'];
|
||||
yield ['HTML'];
|
||||
yield ['fff000'];
|
||||
yield ['000aaa'];
|
||||
yield ['fffaaa'];
|
||||
yield ['#f'];
|
||||
yield ['#ff'];
|
||||
yield ['#ffdd'];
|
||||
yield ['#ffddd'];
|
||||
yield ['#ffddddd'];
|
||||
yield [new \stdClass(), 'object'];
|
||||
yield [[], 'array'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getInvalidColors
|
||||
*/
|
||||
public function testValidationError(mixed $color, ?string $parameterType = null): void
|
||||
{
|
||||
$constraint = new ExportRenderer();
|
||||
|
||||
$this->validator->validate($color, $constraint);
|
||||
|
||||
if (\is_string($color)) {
|
||||
$expectedFormat = '"' . $color . '"';
|
||||
} else {
|
||||
$expectedFormat = $parameterType ?? '';
|
||||
}
|
||||
|
||||
$this->buildViolation('Unknown exporter type.')
|
||||
->setParameter('{{ value }}', $expectedFormat)
|
||||
->setCode(ExportRenderer::UNKNOWN_TYPE)
|
||||
->assertRaised();
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class HexColorValidatorTest extends ConstraintValidatorTestCase
|
||||
return new HexColorValidator();
|
||||
}
|
||||
|
||||
public static function getValidColors()
|
||||
public static function getValidColors(): iterable
|
||||
{
|
||||
yield ['#000'];
|
||||
yield ['#aaa'];
|
||||
@@ -56,7 +56,7 @@ class HexColorValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public static function getInvalidColors()
|
||||
public static function getInvalidColors(): iterable
|
||||
{
|
||||
yield ['string'];
|
||||
yield ['000'];
|
||||
|
||||
@@ -32,7 +32,7 @@ class RoleValidatorTest extends ConstraintValidatorTestCase
|
||||
return new RoleValidator($roleService);
|
||||
}
|
||||
|
||||
public static function getValidRoles()
|
||||
public static function getValidRoles(): array
|
||||
{
|
||||
return [
|
||||
[User::ROLE_USER],
|
||||
@@ -69,7 +69,7 @@ class RoleValidatorTest extends ConstraintValidatorTestCase
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public static function getInvalidRoles()
|
||||
public static function getInvalidRoles(): array
|
||||
{
|
||||
return [
|
||||
['foo'],
|
||||
|
||||
@@ -52,7 +52,7 @@ class TimeFormatValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public static function getValidTimes()
|
||||
public static function getValidTimes(): array
|
||||
{
|
||||
return [
|
||||
[''],
|
||||
@@ -79,7 +79,7 @@ class TimeFormatValidatorTest extends ConstraintValidatorTestCase
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public static function getInvalidTimes()
|
||||
public static function getInvalidTimes(): array
|
||||
{
|
||||
return [
|
||||
['a'],
|
||||
|
||||
@@ -143,7 +143,7 @@ class TimesheetBasicValidatorTest extends ConstraintValidatorTestCase
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public static function getProjectStartEndTestData()
|
||||
public static function getProjectStartEndTestData(): iterable
|
||||
{
|
||||
yield [new \DateTime(), new \DateTime(), [
|
||||
['begin_date', TimesheetBasic::PROJECT_NOT_STARTED, 'The project has not started at that time.'],
|
||||
|
||||
@@ -195,7 +195,7 @@ class TimesheetBudgetUsedValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public static function getViolationTestData()
|
||||
public static function getViolationTestData(): array
|
||||
{
|
||||
return [
|
||||
// activity: violations ----------------------------------------------------------------------
|
||||
@@ -294,7 +294,7 @@ class TimesheetBudgetUsedValidatorTest extends ConstraintValidatorTestCase
|
||||
string $duration,
|
||||
array $rawData = [],
|
||||
?Rate $rate = null
|
||||
) {
|
||||
): void {
|
||||
$activityStatistic = new ActivityStatistic();
|
||||
if ($activityDuration !== null) {
|
||||
$activityStatistic->setDuration($activityDuration);
|
||||
|
||||
@@ -171,7 +171,7 @@ class TimesheetLockdownValidatorTest extends ConstraintValidatorTestCase
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTestData()
|
||||
public static function getTestData(): iterable
|
||||
{
|
||||
// changing before last dockdown period is not allowed
|
||||
yield [false, false, '-5 days', '+5 days', true];
|
||||
@@ -219,7 +219,7 @@ class TimesheetLockdownValidatorTest extends ConstraintValidatorTestCase
|
||||
}
|
||||
}
|
||||
|
||||
public static function getConfigTestData()
|
||||
public static function getConfigTestData(): iterable
|
||||
{
|
||||
yield [false, false, null, null, null, false];
|
||||
yield [false, false, '+5 days', null, null, false];
|
||||
|
||||
@@ -94,7 +94,7 @@ class TimesheetRestartValidatorTest extends ConstraintValidatorTestCase
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTestData()
|
||||
public static function getTestData(): iterable
|
||||
{
|
||||
yield [false, 'end_date', 'default'];
|
||||
yield [true, null, 'default'];
|
||||
|
||||
@@ -58,7 +58,7 @@ class TimesheetZeroDurationValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->validator->validate(new NotBlank(), new TimesheetZeroDuration(['message' => 'Duration cannot be zero.']));
|
||||
}
|
||||
|
||||
private function prepareTimesheet()
|
||||
private function prepareTimesheet(): Timesheet
|
||||
{
|
||||
// creates Timesheet with same begin and endtime
|
||||
$begin = new \DateTime();
|
||||
|
||||
@@ -2816,116 +2816,31 @@ parameters:
|
||||
count: 6
|
||||
path: Utils/StringHelperTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\ColorChoicesValidatorTest\\:\\:getInvalidColors\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/ColorChoicesValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\ColorChoicesValidatorTest\\:\\:getValidColors\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/ColorChoicesValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\DateTimeFormatValidatorTest\\:\\:getInvalidData\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/DateTimeFormatValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\DateTimeFormatValidatorTest\\:\\:getValidData\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/DateTimeFormatValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function strtoupper expects string, int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/DurationValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\HexColorValidatorTest\\:\\:getInvalidColors\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/HexColorValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\HexColorValidatorTest\\:\\:getValidColors\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/HexColorValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Cannot cast mixed to string\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/RoleValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\RoleValidatorTest\\:\\:getInvalidRoles\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/RoleValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\RoleValidatorTest\\:\\:getValidRoles\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/RoleValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimeFormatValidatorTest\\:\\:getInvalidTimes\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimeFormatValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimeFormatValidatorTest\\:\\:getValidTimes\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimeFormatValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method assertRaised\\(\\) on Symfony\\\\Component\\\\Validator\\\\Test\\\\ConstraintViolationAssertion\\|null\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetBasicValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetBasicValidatorTest\\:\\:getProjectStartEndTestData\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetBasicValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetBasicValidatorTest\\:\\:testEndBeforeWithProjectStartAndEnd\\(\\) has parameter \\$violations with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetBasicValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetBudgetUsedValidatorTest\\:\\:getViolationTestData\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetBudgetUsedValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetBudgetUsedValidatorTest\\:\\:testWithActivityTimeBudget\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetBudgetUsedValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetBudgetUsedValidatorTest\\:\\:testWithActivityTimeBudget\\(\\) has parameter \\$rawData with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetBudgetUsedValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetLockdownValidatorTest\\:\\:getConfigTestData\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetLockdownValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetLockdownValidatorTest\\:\\:getTestData\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetLockdownValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetRestartValidatorTest\\:\\:getTestData\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetRestartValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Validator\\\\Constraints\\\\TimesheetZeroDurationValidatorTest\\:\\:prepareTimesheet\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Validator/Constraints/TimesheetZeroDurationValidatorTest.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\<T of object\\>\\|T of object, string given\\.$#"
|
||||
count: 1
|
||||
|
||||
Reference in New Issue
Block a user