configurable csv/xlsx export templates (#5531)

This commit is contained in:
Kevin Papst
2025-06-11 13:06:33 +02:00
committed by GitHub
parent 46129c7ab9
commit 05aaa1950a
83 changed files with 2632 additions and 412 deletions

View 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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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);
}