Release 2.38.0 (#5563)

This commit is contained in:
Kevin Papst
2025-08-08 23:25:42 +02:00
committed by GitHub
parent f3890691ce
commit 04331420ae
88 changed files with 1319 additions and 697 deletions

View File

@@ -487,6 +487,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
'color' => '@string',
'customer' => 'int',
'number' => '@int',
'orderNumber' => '@string',
'globalActivities' => 'bool',
'comment' => '@string',
];
@@ -501,6 +502,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
'color' => '@string',
'customer' => ['result' => 'object', 'type' => 'Customer'],
'number' => '@int',
'orderNumber' => '@string',
'globalActivities' => 'bool',
'comment' => '@string',
];
@@ -514,6 +516,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
'billable' => 'bool',
'customer' => 'int',
'number' => '@int',
'orderNumber' => '@string',
'color' => '@string',
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
'parentTitle' => 'string',

View File

@@ -100,7 +100,7 @@ class ExportControllerTest extends AbstractControllerBaseTestCase
$titles[] = trim($th->textContent);
}
self::assertEquals([
'', 'Date', 'User', 'Project', 'Activity', 'Description', 'Tags', 'Duration', 'Unit price', 'Internal price', 'Total price', '',
'', 'Date', 'From', 'To', 'User', 'Project', 'Activity', 'Description', 'Tags', 'Duration', 'Unit price', 'Internal price', 'Total price', '',
], $titles);
// assert export type buttons are available

View File

@@ -49,7 +49,10 @@ class UserTest extends TestCase
self::assertFalse($user->canSeeAllData());
self::assertFalse($user->isExportDecimal());
self::assertFalse($user->isSystemAccount());
self::assertFalse($user->isPasswordRequestNonExpired(-1));
self::assertFalse($user->isPasswordRequestNonExpired(0));
self::assertFalse($user->isPasswordRequestNonExpired(3599));
self::assertFalse($user->isPasswordRequestNonExpired(PHP_INT_MAX));
$user->setUserIdentifier('foo');
self::assertEquals('foo', $user->getUserIdentifier());
@@ -675,4 +678,21 @@ class UserTest extends TestCase
self::assertInstanceOf(\DateTime::class, $lastLogin);
self::assertEquals('Europe/Berlin', $lastLogin->getTimezone()->getName());
}
public function testIsPasswordRequestNonExpiredIsTimezoneIndependent(): void
{
$user = new User();
$user->setTimezone('Europe/Vienna');
$user->markPasswordRequested();
self::assertTrue($user->isPasswordRequestNonExpired(3600));
self::assertTrue($user->isPasswordRequestNonExpired(7200));
$before = date_default_timezone_get();
date_default_timezone_set('America/Los_Angeles');
date_default_timezone_set($before);
self::assertTrue($user->isPasswordRequestNonExpired(3600));
self::assertTrue($user->isPasswordRequestNonExpired(7200));
}
}

View File

@@ -20,6 +20,7 @@ use PHPUnit\Framework\TestCase;
/**
* @covers \App\Event\InvoiceCreatedEvent
* @covers \App\Event\AbstractInvoiceEvent
*/
class InvoiceCreatedEventTest extends TestCase
{

View File

@@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
/**
* @covers \App\Event\InvoiceDeleteEvent
* @covers \App\Event\AbstractInvoiceEvent
*/
class InvoiceDeleteEventTest extends TestCase
{

View File

@@ -14,6 +14,7 @@ use App\Entity\ExportTemplate;
use App\Form\ExportTemplateSpreadsheetForm;
use App\Form\Type\ExportColumnsType;
use App\Form\Type\LanguageType;
use App\Tests\Mocks\SystemConfigurationFactory;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\Form\Test\TypeTestCase;
@@ -31,9 +32,10 @@ class ExportTemplateSpreadsheetFormTest extends TypeTestCase
{
$dispatcher = $this->createMock(EventDispatcherInterface::class);
$translator = $this->createMock(TranslatorInterface::class);
$config = SystemConfigurationFactory::createStub();
return [
new ExportColumnsType($dispatcher, $translator),
new ExportColumnsType($dispatcher, $translator, $config),
new LanguageType(new LocaleService([]))
];
}

View File

@@ -11,6 +11,7 @@ namespace App\Tests\Form\Type;
use App\Form\Type\ExportColumnsType;
use App\Tests\Mocks\MetaFieldColumnSubscriberMock;
use App\Tests\Mocks\SystemConfigurationFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Test\TypeTestCase;
@@ -40,9 +41,10 @@ class ExportColumnsTypeTest extends TypeTestCase
$dispatcher->addSubscriber(new MetaFieldColumnSubscriberMock());
$translator = $this->createMock(TranslatorInterface::class);
$config = SystemConfigurationFactory::createStub();
return [
new ExportColumnsType($dispatcher, $translator)
new ExportColumnsType($dispatcher, $translator, $config)
];
}

View File

@@ -26,7 +26,7 @@ class MarkdownExtensionTest extends TestCase
$config = SystemConfigurationFactory::create($loader, ['timesheet' => ['markdown_content' => true]]);
$sut = new MarkdownExtension(new Markdown(), $config);
self::assertEquals('<p><em>test</em></p>', $sut->markdownToHtml('*test*'));
self::assertEquals('<h1>foobar</h1>', $sut->markdownToHtml('# foobar'));
self::assertEquals('<h1 id="foobar">foobar</h1>', $sut->markdownToHtml('# foobar'));
self::assertEquals(
'<p><a href="javascript%3Aalert(`XSS`)">XSS</a></p>',
$sut->markdownToHtml('[XSS](javascript:alert(`XSS`))')

View File

@@ -53,7 +53,6 @@ class ThemeEventExtensionTest extends TestCase
protected function getSut(bool $hasListener = true): ThemeExtension
{
$dispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher->method('hasListeners')->willReturn($hasListener);
$dispatcher->expects($hasListener ? $this->once() : $this->never())->method('dispatch');
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
@@ -95,13 +94,6 @@ class ThemeEventExtensionTest extends TestCase
self::assertInstanceOf(ThemeEvent::class, $event);
}
public function testTriggerWithoutListener(): void
{
$sut = $this->getSut(false);
$event = $sut->trigger($this->getEnvironment(), 'foo', []);
self::assertInstanceOf(ThemeEvent::class, $event);
}
public function testJavascriptTranslations(): void
{
$sut = $this->getSut();

View File

@@ -79,7 +79,6 @@ class RuntimeExtensionsTest extends TestCase
foreach ($filters as $filter) {
switch ($filter->getName()) {
case 'md2html':
self::assertEquals('html', $filters[0]->getPreEscape());
self::assertEquals(['html'], $filters[0]->getSafe(new TextNode('', 10)));
$found_md2html = true;
break;

View File

@@ -0,0 +1,42 @@
<?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\Utils;
use App\Utils\ParsedownExtension;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Utils\Parsedown
* @covers \App\Utils\ParsedownExtension
*/
class ParsedownExtensionTest extends TestCase
{
public function testTableContainsCssClasses(): void
{
$sut = new ParsedownExtension();
$html = $sut->parse('
| Item | Price |
|---|---|
| Something | $ 472,78 |
| Another entry | € 111 |
| | |
| Total | A lot |');
self::assertStringStartsWith('<table class="table">', $html);
}
public function testHeaderIsNotConverted(): void
{
$sut = new ParsedownExtension();
$html = $sut->parse('
# Foo
');
self::assertEquals('<p># Foo</p>', $html);
}
}

View File

@@ -0,0 +1,54 @@
<?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\Utils;
use App\Utils\Parsedown;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Utils\Parsedown
*/
class ParsedownTest extends TestCase
{
public function testTableContainsCssClasses(): void
{
$sut = new Parsedown();
$html = $sut->parse('
| Item | Price |
|---|---|
| Something | $ 472,78 |
| Another entry | € 111 |
| | |
| Total | A lot |');
self::assertStringStartsWith('<table class="table table-striped table-vcenter">', $html);
}
public function testHeaderContainsId(): void
{
$sut = new Parsedown();
$html = $sut->parse('
# Foo
');
self::assertEquals('<h1 id="foo">Foo</h1>', $html);
}
public function testHeaderContainsIdDoesNotDuplicate(): void
{
$sut = new Parsedown();
$html = $sut->parse('
# Foo
# Foo
# Foo
');
self::assertEquals('<h1 id="foo">Foo</h1>
<h1 id="foo-1">Foo</h1>
<h1 id="foo-2">Foo</h1>', $html);
}
}

View File

@@ -0,0 +1,47 @@
<?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\Webhook\Attribute;
use App\Entity\Invoice;
use App\Event\InvoiceDeleteEvent;
use App\Webhook\Attribute\AsWebhook;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Webhook\Attribute\AsWebhook
*/
class AsWebhookTestCase extends TestCase
{
public function testConstruct(): void
{
$attribute = new AsWebhook('name', 'description', 'some payload');
self::assertEquals('name', $attribute->name);
self::assertEquals('description', $attribute->description);
self::assertEquals('some payload', $attribute->payload);
}
public function testUsage(): void
{
$invoice = new Invoice();
$invoice->setComment('foo bar');
$usage = new InvoiceDeleteEvent($invoice);
$ref = new \ReflectionClass($usage);
$attr = $ref->getAttributes(AsWebhook::class);
self::assertCount(1, $attr);
$arguments = $attr[0]->getArguments();
self::assertEquals('invoice.deleted', $arguments['name']);
self::assertEquals('Triggered after an invoice was deleted', $arguments['description']);
self::assertEquals('object.getInvoice()', $arguments['payload']);
}
}