refactored invoice and export screens (#1046)

This commit is contained in:
Kevin Papst
2019-08-22 18:56:21 +02:00
committed by GitHub
parent 405ea0076b
commit 46e5650882
55 changed files with 930 additions and 906 deletions

View File

@@ -9,8 +9,10 @@
namespace App\Tests\Controller;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Tests\DataFixtures\TimesheetFixtures;
use Doctrine\ORM\EntityManager;
/**
* @group integration
@@ -27,7 +29,7 @@ class ExportControllerTest extends ControllerBaseTest
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$this->request($client, '/export/');
$this->request($client, '/export/?preview=');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertHasNoEntriesWithFilter($client);
@@ -47,7 +49,7 @@ class ExportControllerTest extends ControllerBaseTest
;
$this->importFixture($em, $fixture);
$this->request($client, '/export/');
$this->request($client, '/export/?preview=');
$this->assertTrue($client->getResponse()->isSuccessful());
// make sure all existing records are displayed
@@ -55,7 +57,7 @@ class ExportControllerTest extends ControllerBaseTest
$this->assertDataTableRowCount($client, 'datatable_export', 20);
// assert export type buttons are available
$expected = ['csv', 'html', 'pdf', 'ods', 'xlsx'];
$expected = ['csv', 'html', 'pdf', 'xlsx'];
$node = $client->getCrawler()->filter('#export-buttons button');
$this->assertEquals(count($expected), $node->count());
foreach ($node->getIterator() as $button) {
@@ -100,6 +102,7 @@ class ExportControllerTest extends ControllerBaseTest
public function testExportAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
/** @var EntityManager $em */
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$begin = new \DateTime('first day of this month');
@@ -121,7 +124,8 @@ class ExportControllerTest extends ControllerBaseTest
// don't add daterange to make sure the current month is the default range
$client->submit($form, [
'type' => 'html'
'type' => 'html',
'markAsExported' => 1
]);
$response = $client->getResponse();
@@ -137,5 +141,11 @@ class ExportControllerTest extends ControllerBaseTest
$node = $client->getCrawler()->filter('section.export div#export-records table.dataTable tbody tr');
// 20 rows + the summary footer
$this->assertEquals(21, $node->count());
$timesheets = $em->getRepository(Timesheet::class)->findAll();
/** @var Timesheet $timesheet */
foreach ($timesheets as $timesheet) {
$this->assertTrue($timesheet->isExported());
}
}
}

View File

@@ -10,10 +10,12 @@
namespace App\Tests\Controller;
use App\Entity\InvoiceTemplate;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Form\Type\DateRangeType;
use App\Tests\DataFixtures\InvoiceFixtures;
use App\Tests\DataFixtures\TimesheetFixtures;
use Doctrine\ORM\EntityManager;
/**
* @group integration
@@ -28,7 +30,7 @@ class InvoiceControllerTest extends ControllerBaseTest
public function testIndexActionRedirectsToCreateTemplate()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->request($client, '/invoice/');
$this->assertIsRedirect($client, '/invoice/template/create');
@@ -42,7 +44,7 @@ class InvoiceControllerTest extends ControllerBaseTest
$fixture = new InvoiceFixtures();
$this->importFixture($em, $fixture);
$this->request($client, '/invoice/');
$this->request($client, '/invoice/?preview=');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertHasNoEntriesWithFilter($client);
@@ -50,7 +52,7 @@ class InvoiceControllerTest extends ControllerBaseTest
public function testListTemplateAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new InvoiceFixtures();
@@ -64,7 +66,7 @@ class InvoiceControllerTest extends ControllerBaseTest
public function testCreateTemplateAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->request($client, '/invoice/template/create');
$this->assertTrue($client->getResponse()->isSuccessful());
@@ -88,7 +90,7 @@ class InvoiceControllerTest extends ControllerBaseTest
public function testCopyTemplateAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new InvoiceFixtures();
@@ -117,6 +119,7 @@ class InvoiceControllerTest extends ControllerBaseTest
public function testPrintAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
/** @var EntityManager $em */
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new InvoiceFixtures();
@@ -138,66 +141,50 @@ class InvoiceControllerTest extends ControllerBaseTest
$dateRange = $begin->format('Y-m-d') . DateRangeType::DATE_SPACER . $end->format('Y-m-d');
$form = $client->getCrawler()->filter('#invoice-print-form')->form();
$node = $form->getFormNode();
$node->setAttribute('action', $this->createUrl('/invoice/?preview='));
$node->setAttribute('method', 'GET');
$client->submit($form, [
'template' => 1,
'user' => '',
'daterange' => $dateRange,
'customer' => 1,
]);
$this->assertTrue($client->getResponse()->isSuccessful());
// no datatable should be displayed
// no warning should be displayed
$node = $client->getCrawler()->filter('div.callout.callout-warning.lead');
$this->assertEquals(0, $node->count());
$node = $client->getCrawler()->filter('div.callout.callout-success.lead');
$this->assertNotEmpty($node->text());
$this->assertContains('This is a preview of the data that will show up in your invoice document.', $node->text());
// but the datatable with all timesheets
$this->assertDataTableRowCount($client, 'datatable_invoice', 20);
$form = $client->getCrawler()->filter('#invoice-print-form')->form();
$node = $form->getFormNode();
$node->setAttribute('action', $this->createUrl('/invoice/print'));
$node->setAttribute('method', 'POST');
$node->setAttribute('action', $this->createUrl('/invoice/?create='));
$node->setAttribute('method', 'GET');
$client->submit($form, [
'template' => 1,
'user' => '',
'daterange' => $dateRange,
'customer' => 1,
'project' => 1,
'markAsExported' => 1,
]);
$this->assertTrue($client->getResponse()->isSuccessful());
$node = $client->getCrawler()->filter('body');
$this->assertEquals(1, $node->count());
$this->assertEquals('invoice_print', $node->getIterator()[0]->getAttribute('class'));
}
public function testPrintActionRedirectsToCreateTemplate()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$this->request($client, '/invoice/print', 'POST');
$this->assertIsRedirect($client, '/invoice/template/create');
}
public function testPrintActionRedirectsToIndex()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new InvoiceFixtures();
$this->importFixture($em, $fixture);
$this->request($client, '/invoice/print', 'POST');
$this->assertIsRedirect($client, '/invoice/');
$timesheets = $em->getRepository(Timesheet::class)->findAll();
/** @var Timesheet $timesheet */
foreach ($timesheets as $timesheet) {
$this->assertTrue($timesheet->isExported());
}
}
public function testEditTemplateAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
$fixture = new InvoiceFixtures();
@@ -231,8 +218,8 @@ class InvoiceControllerTest extends ControllerBaseTest
$fixture = new InvoiceFixtures();
$this->importFixture($em, $fixture);
$this->request($client, '/invoice/template/1/delete?page=1');
$this->assertIsRedirect($client, '/invoice/template/page/1');
$this->request($client, '/invoice/template/1/delete');
$this->assertIsRedirect($client, '/invoice/template');
$client->followRedirect();
$this->assertTrue($client->getResponse()->isSuccessful());

View File

@@ -210,6 +210,6 @@ class UserControllerTest extends ControllerBaseTest
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$this->assertAccessIsGranted($client, '/admin/user/permissions');
$this->assertHasDataTable($client);
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 84);
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 80);
}
}

View File

@@ -20,7 +20,6 @@ use App\Entity\User;
use App\Export\RendererInterface;
use App\Repository\Query\TimesheetQuery;
use App\Twig\DateExtensions;
use App\Twig\Extensions;
use App\Utils\LocaleSettings;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
@@ -52,9 +51,8 @@ abstract class AbstractRendererTest extends KernelTestCase
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
$dateExtension = new DateExtensions($localeSettings);
$extensions = new Extensions($localeSettings);
return new $classname($translator, $dateExtension, $extensions);
return new $classname($translator, $dateExtension);
}
/**

View File

@@ -32,7 +32,7 @@ class CsvRendererTest extends AbstractRendererTest
public function getTestModel()
{
return [
['01:50 h', '€2,437.12', '1,947.99', 7, 5, 1, 2, 2]
['400', '2437.12', ' EUR 1,947.99 ', 7, 5, 1, 2, 2]
];
}
@@ -76,26 +76,27 @@ class CsvRendererTest extends AbstractRendererTest
}
$expected = [
0 => '2019.06.16 12:00',
1 => '2019.06.16 12:06',
2 => 'kevin',
3 => 'Customer Name',
4 => 'project name',
5 => 'activity description',
6 => '',
7 => '',
8 => 'foo,bar',
9 => 'meta-bar',
10 => 'meta-bar2',
11 => '€0.00',
12 => '€84.00',
13 => '00:06 h',
14 => '€0.00',
0 => '2019-06-16',
1 => '12:00',
2 => '12:06',
3 => '400',
4 => '0',
5 => 'kevin',
6 => 'Customer Name',
7 => 'project name',
8 => 'activity description',
9 => '',
10 => '',
11 => 'foo,bar',
12 => '',
13 => ' EUR 84.00 ',
14 => 'meta-bar',
15 => 'meta-bar2',
];
self::assertEquals(7, count($all));
self::assertEquals(count($expected), count($all[0]));
self::assertEquals('foo', $all[4][8]);
self::assertEquals('foo', $all[4][11]);
self::assertEquals($expected, $all[5]);
}

View File

@@ -1,52 +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\Export\Renderer;
use App\Export\Renderer\OdsRenderer;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* @covers \App\Export\Renderer\OdsRenderer
* @covers \App\Export\Renderer\AbstractSpreadsheetRenderer
* @covers \App\Export\Renderer\RendererTrait
* @group integration
*/
class OdsRendererTest extends AbstractRendererTest
{
public function testConfiguration()
{
$sut = $this->getAbstractRenderer(OdsRenderer::class);
$this->assertEquals('ods', $sut->getId());
$this->assertEquals('ods', $sut->getTitle());
$this->assertEquals('ods', $sut->getIcon());
}
public function testRender()
{
$sut = $this->getAbstractRenderer(OdsRenderer::class);
/** @var BinaryFileResponse $response */
$response = $this->render($sut);
$file = $response->getFile();
$this->assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $response->headers->get('Content-Type'));
$this->assertEquals('attachment; filename=kimai-export.ods', $response->headers->get('Content-Disposition'));
$this->assertTrue(file_exists($file->getRealPath()));
ob_start();
$response->sendContent();
$content2 = ob_get_clean();
$this->assertNotEmpty($content2);
$this->assertFalse(file_exists($file->getRealPath()));
}
}

View File

@@ -51,7 +51,7 @@ class DebugRendererTest extends TestCase
// TODO check values or formats?
}
protected function assertModelStructure(array $model, $hasProject = true, $hasActivity = false)
protected function assertModelStructure(array $model, $hasProject = true)
{
$keys = [
'invoice.due_date',
@@ -85,10 +85,14 @@ class DebugRendererTest extends TestCase
'customer.number',
'customer.homepage',
'customer.comment',
'customer.fixed_rate',
'customer.hourly_rate',
'customer.meta.foo-customer',
'activity.id',
'activity.name',
'activity.comment',
'activity.fixed_rate',
'activity.hourly_rate',
'activity.meta.foo-activity',
];
@@ -98,18 +102,12 @@ class DebugRendererTest extends TestCase
'project.name',
'project.comment',
'project.order_number',
'project.fixed_rate',
'project.hourly_rate',
'project.meta.foo-project',
]);
}
if ($hasActivity) {
$keys = array_merge($keys, [
'activity.id',
'activity.name',
'activity.comment',
]);
}
$givenKeys = array_keys($model);
sort($keys);
sort($givenKeys);

View File

@@ -37,6 +37,15 @@ class ExportQueryTest extends BaseQueryTest
$this->assertState($sut);
$this->assertExported($sut);
$this->assertType($sut);
$this->assertMarkAsExported($sut);
}
protected function assertMarkAsExported(ExportQuery $sut)
{
$this->assertFalse($sut->isMarkAsExported());
$sut->setMarkAsExported(true);
$this->assertTrue($sut->isMarkAsExported());
}
protected function assertUser(ExportQuery $sut)

View File

@@ -0,0 +1,123 @@
<?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\Repository\Query;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\User;
use App\Repository\Query\InvoiceQuery;
/**
* @covers \App\Repository\Query\InvoiceQuery
*/
class InvoiceQueryTest extends BaseQueryTest
{
public function testQuery()
{
$sut = new InvoiceQuery();
$this->assertResultType($sut);
$this->assertPage($sut);
$this->assertPageSize($sut);
$this->assertOrderBy($sut, 'begin');
$this->assertOrder($sut, InvoiceQuery::ORDER_DESC);
$this->assertUser($sut);
$this->assertCustomer($sut);
$this->assertProject($sut);
$this->assertActivity($sut);
$this->assertState($sut);
$this->assertExported($sut);
$this->assertMarkAsExported($sut);
}
protected function assertMarkAsExported(InvoiceQuery $sut)
{
$this->assertFalse($sut->isMarkAsExported());
$sut->setMarkAsExported(true);
$this->assertTrue($sut->isMarkAsExported());
}
protected function assertUser(InvoiceQuery $sut)
{
$this->assertNull($sut->getUser());
$expected = new User();
$expected->setUsername('foo-bar');
$sut->setUser($expected);
$this->assertEquals($expected, $sut->getUser());
}
protected function assertCustomer(InvoiceQuery $sut)
{
$this->assertNull($sut->getCustomer());
$expected = new Customer();
$expected->setName('foo-bar');
$sut->setCustomer($expected);
$this->assertEquals($expected, $sut->getCustomer());
}
protected function assertProject(InvoiceQuery $sut)
{
$this->assertNull($sut->getProject());
$expected = new Project();
$expected->setName('foo-bar');
$sut->setProject($expected);
$this->assertEquals($expected, $sut->getProject());
}
protected function assertActivity(InvoiceQuery $sut)
{
$this->assertNull($sut->getActivity());
$expected = new Activity();
$expected->setName('foo-bar');
$sut->setActivity($expected);
$this->assertEquals($expected, $sut->getActivity());
}
protected function assertState(InvoiceQuery $sut)
{
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getState());
$sut->setState(PHP_INT_MAX);
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getState());
$sut->setState(InvoiceQuery::STATE_STOPPED);
$this->assertEquals(InvoiceQuery::STATE_STOPPED, $sut->getState());
$sut->setState(InvoiceQuery::STATE_RUNNING);
$this->assertEquals(InvoiceQuery::STATE_RUNNING, $sut->getState());
$sut->setState(InvoiceQuery::STATE_ALL);
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getState());
}
protected function assertExported(InvoiceQuery $sut)
{
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getExported());
$sut->setExported(PHP_INT_MAX);
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getExported());
$sut->setExported(InvoiceQuery::STATE_EXPORTED);
$this->assertEquals(InvoiceQuery::STATE_EXPORTED, $sut->getExported());
$sut->setExported(InvoiceQuery::STATE_NOT_EXPORTED);
$this->assertEquals(InvoiceQuery::STATE_NOT_EXPORTED, $sut->getExported());
$sut->setExported(InvoiceQuery::STATE_ALL);
$this->assertEquals(InvoiceQuery::STATE_ALL, $sut->getExported());
}
}

View File

@@ -31,6 +31,7 @@ class TimesheetQueryTest extends BaseQueryTest
$this->assertOrder($sut, TimesheetQuery::ORDER_DESC);
$this->assertUser($sut);
$this->assertUsers($sut);
$this->assertCustomer($sut);
$this->assertProject($sut);
$this->assertActivity($sut);
@@ -48,6 +49,30 @@ class TimesheetQueryTest extends BaseQueryTest
$this->assertEquals($expected, $sut->getUser());
}
protected function assertUsers(TimesheetQuery $sut)
{
$this->assertEmpty($sut->getUsers());
$user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock();
$user->method('getId')->willReturn(1);
$sut->addUser($user);
$user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock();
$user->method('getId')->willReturn(1);
$sut->addUser($user);
$user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock();
$user->method('getId')->willReturn(13);
$sut->addUser($user);
$user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock();
$user->method('getId')->willReturn(27);
$sut->addUser($user);
$sut->removeUser($user);
$this->assertCount(2, $sut->getUsers());
}
protected function assertCustomer(TimesheetQuery $sut)
{
$this->assertNull($sut->getCustomer());

View File

@@ -73,7 +73,7 @@ abstract class AbstractVoterTest extends TestCase
$customers = ['view_customer', 'edit_customer', 'budget_customer', 'delete_customer', 'create_customer'];
$customersTeam = ['view_customer', 'edit_teamlead_customer', 'budget_teamlead_customer'];
$invoice = ['view_invoice', 'create_invoice'];
$invoiceTemplate = ['view_invoice_template', 'create_invoice_template', 'edit_invoice_template', 'delete_invoice_template'];
$invoiceTemplate = ['manage_invoice_template'];
$timesheet = ['view_own_timesheet', 'start_own_timesheet', 'stop_own_timesheet', 'create_own_timesheet', 'edit_own_timesheet', 'export_own_timesheet', 'delete_own_timesheet'];
$timesheetOthers = ['view_other_timesheet', 'start_other_timesheet', 'stop_other_timesheet', 'create_other_timesheet', 'edit_other_timesheet', 'export_other_timesheet', 'delete_other_timesheet'];
$profile = ['view_own_profile', 'edit_own_profile', 'password_own_profile', 'preferences_own_profile', 'api-token_own_profile'];

View File

@@ -1,66 +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\Voter;
use App\Entity\InvoiceTemplate;
use App\Entity\User;
use App\Voter\InvoiceTemplateVoter;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
/**
* @covers \App\Voter\InvoiceTemplateVoter
*/
class InvoiceTemplateVoterTest extends AbstractVoterTest
{
/**
* @dataProvider getTestData
*/
public function testVote(User $user, $subject, $attribute, $result)
{
$token = new UsernamePasswordToken($user, 'foo', 'bar', $user->getRoles());
$sut = $this->getVoter(InvoiceTemplateVoter::class, $user);
$this->assertEquals($result, $sut->vote($token, $subject, [$attribute]));
}
public function getTestData()
{
$user0 = $this->getUser(0, null);
$user1 = $this->getUser(1, User::ROLE_USER);
$user2 = $this->getUser(2, User::ROLE_TEAMLEAD);
$user3 = $this->getUser(3, User::ROLE_ADMIN);
$user4 = $this->getUser(4, User::ROLE_SUPER_ADMIN);
$result = VoterInterface::ACCESS_GRANTED;
foreach ([$user3, $user4] as $user) {
yield [$user, new InvoiceTemplate(), 'view', $result];
yield [$user, new InvoiceTemplate(), 'edit', $result];
yield [$user, new InvoiceTemplate(), 'delete', $result];
}
$result = VoterInterface::ACCESS_DENIED;
foreach ([$user0, $user1, $user2] as $user) {
yield [$user, new InvoiceTemplate(), 'view', $result];
yield [$user, new InvoiceTemplate(), 'edit', $result];
yield [$user, new InvoiceTemplate(), 'delete', $result];
}
$result = VoterInterface::ACCESS_ABSTAIN;
foreach ([$user0, $user1, $user2] as $user) {
yield [$user, new InvoiceTemplate(), 'view_invoice_template', $result];
yield [$user, new InvoiceTemplate(), 'edit_invoice_template', $result];
yield [$user, new InvoiceTemplate(), 'delete_invoice_template', $result];
yield [$user, new \stdClass(), 'view', $result];
yield [$user, null, 'edit', $result];
yield [$user, $user, 'delete', $result];
}
}
}

View File

@@ -40,7 +40,7 @@ class RolePermissionVoterTest extends AbstractVoterTest
$user4 = $this->getUser(4, User::ROLE_SUPER_ADMIN);
$invoice = [
'create_invoice_template' => null,
'manage_invoice_template' => null,
'view_invoice' => null,
'create_invoice' => null,
];

View File

@@ -4,6 +4,7 @@ includes:
- %rootDir%/../phpstan-phpunit/extension.neon
parameters:
tmpDir: %rootDir%/../../../var/cache/phpstan
ignoreErrors:
- '#Access to an undefined property Faker\\Generator::\$stateAbbr.#'
- '#Access to an undefined property Faker\\Generator::\$catchPhrase.#'