mirror of
https://github.com/kevinpapst/kimai2.git
synced 2025-03-16 05:53:29 +00:00
Merge branch 'main' into feature-console-template-parameter-overwrite
This commit is contained in:
commit
f29d6d4833
26 changed files with 271 additions and 36 deletions
.github/workflows
assets/js
src
Controller
Export
Form
Repository
Timesheet
Twig
Utils
Widget/Type
templates
tests
Export
Base
Package/CellFormatter
Twig
2
.github/workflows/testing.yaml
vendored
2
.github/workflows/testing.yaml
vendored
|
@ -104,7 +104,7 @@ jobs:
|
|||
|
||||
- name: Upload code coverage
|
||||
if: matrix.php == '8.2'
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./coverage.xml
|
||||
|
|
|
@ -49,4 +49,11 @@ export default class KimaiUser extends KimaiPlugin {
|
|||
return this.user.superAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {array}
|
||||
*/
|
||||
getRoles() {
|
||||
return this.user.roles;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ export default class KimaiPaginatedBoxWidget {
|
|||
constructor(boxId) {
|
||||
this.selector = boxId;
|
||||
const widget = document.querySelector(this.selector);
|
||||
this.href = widget.dataset['href'];
|
||||
|
||||
if (widget.dataset['reload'] !== undefined) {
|
||||
this.events = widget.dataset['reload'].split(' ');
|
||||
|
@ -93,7 +92,7 @@ export default class KimaiPaginatedBoxWidget {
|
|||
if (node.tagName !== undefined && node.tagName === 'SCRIPT') {
|
||||
const script = document.createElement('script');
|
||||
script.text = node.innerHTML;
|
||||
node.parentNode.replaceChild(script, node );
|
||||
node.parentNode.replaceChild(script, node);
|
||||
} else {
|
||||
for (const child of node.childNodes) {
|
||||
this._makeScriptExecutable(child);
|
||||
|
|
|
@ -106,11 +106,11 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||
}
|
||||
|
||||
if ($canSeeUsername) {
|
||||
$table->addColumn('username', ['class' => 'd-none d-md-table-cell', 'orderBy' => false]);
|
||||
$table->addColumn('username', ['class' => 'd-none d-md-table-cell', 'orderBy' => 'user']);
|
||||
}
|
||||
|
||||
$table->addColumn('billable', ['class' => 'text-center d-none w-min', 'orderBy' => false]);
|
||||
$table->addColumn('exported', ['class' => 'text-center d-none w-min', 'orderBy' => false]);
|
||||
$table->addColumn('billable', ['class' => 'text-center d-none w-min']);
|
||||
$table->addColumn('exported', ['class' => 'text-center d-none w-min']);
|
||||
$table->addColumn('actions', ['class' => 'actions']);
|
||||
|
||||
$page = $this->createPageSetup();
|
||||
|
|
|
@ -45,6 +45,7 @@ final class TimesheetTeamController extends TimesheetAbstractController
|
|||
public function indexAction(int $page, Request $request): Response
|
||||
{
|
||||
$query = $this->createDefaultQuery();
|
||||
$query->addAllowedOrderColumns('user');
|
||||
$query->setPage($page);
|
||||
|
||||
return $this->index($query, $request, 'admin_timesheet', 'admin_timesheet_paginated', TimesheetMetaDisplayEvent::TEAM_TIMESHEET);
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace App\Export\Base;
|
|||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\User;
|
||||
use App\Event\ActivityMetaDisplayEvent;
|
||||
use App\Event\CustomerMetaDisplayEvent;
|
||||
use App\Event\MetaDisplayEventInterface;
|
||||
|
@ -22,6 +23,7 @@ use App\Export\Package\CellFormatter\BooleanFormatter;
|
|||
use App\Export\Package\CellFormatter\CellFormatterInterface;
|
||||
use App\Export\Package\CellFormatter\DateFormatter;
|
||||
use App\Export\Package\CellFormatter\DefaultFormatter;
|
||||
use App\Export\Package\CellFormatter\DurationDecimalFormatter;
|
||||
use App\Export\Package\CellFormatter\DurationFormatter;
|
||||
use App\Export\Package\CellFormatter\RateFormatter;
|
||||
use App\Export\Package\CellFormatter\TextFormatter;
|
||||
|
@ -130,6 +132,7 @@ final class SpreadsheetRenderer
|
|||
'date' => new DateFormatter(),
|
||||
'time' => new TimeFormatter(),
|
||||
'duration' => new DurationFormatter(),
|
||||
'duration_decimal' => new DurationDecimalFormatter(),
|
||||
default => new DefaultFormatter()
|
||||
};
|
||||
}
|
||||
|
@ -141,12 +144,17 @@ final class SpreadsheetRenderer
|
|||
{
|
||||
$showRates = $this->isRenderRate($query);
|
||||
|
||||
$durationFormatter = 'duration';
|
||||
if (($user = $this->voter->getUser()) instanceof User) {
|
||||
$durationFormatter = $user->isExportDecimal() ? 'duration_decimal' : 'duration';
|
||||
}
|
||||
|
||||
$columns = [];
|
||||
|
||||
$columns[] = (new Column('date', $this->getFormatter('date')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getBegin());
|
||||
$columns[] = (new Column('begin', $this->getFormatter('time')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getBegin())->withColumnWidth(ColumnWidth::SMALL);
|
||||
$columns[] = (new Column('end', $this->getFormatter('time')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getEnd())->withColumnWidth(ColumnWidth::SMALL);
|
||||
$columns[] = (new Column('duration', $this->getFormatter('duration')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getDuration())->withColumnWidth(ColumnWidth::SMALL);
|
||||
$columns[] = (new Column('duration', $this->getFormatter($durationFormatter)))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getDuration())->withColumnWidth(ColumnWidth::SMALL);
|
||||
|
||||
if ($showRates) {
|
||||
$columns[] = (new Column('currency', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getProject()?->getCustomer()?->getCurrency())->withColumnWidth(ColumnWidth::SMALL);
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?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\Export\Package\CellFormatter;
|
||||
|
||||
use App\Utils\Duration;
|
||||
|
||||
final class DurationDecimalFormatter implements CellFormatterInterface
|
||||
{
|
||||
private Duration $duration;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->duration = new Duration();
|
||||
}
|
||||
|
||||
public function formatValue(mixed $value): mixed
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
return $this->duration->formatDecimal((int) $value);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
}
|
|
@ -9,14 +9,23 @@
|
|||
|
||||
namespace App\Export\Package\CellFormatter;
|
||||
|
||||
use App\Utils\Duration;
|
||||
|
||||
final class DurationFormatter implements CellFormatterInterface
|
||||
{
|
||||
private Duration $duration;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->duration = new Duration();
|
||||
}
|
||||
|
||||
public function formatValue(mixed $value): mixed
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
return (float) number_format($value / 3600, 2, '.', '');
|
||||
return $this->duration->format((int) $value);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
return $this->duration->format(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use App\Form\Type\DescriptionType;
|
|||
use App\Form\Type\DurationType;
|
||||
use App\Form\Type\FixedRateType;
|
||||
use App\Form\Type\HourlyRateType;
|
||||
use App\Form\Type\InternalRateType;
|
||||
use App\Form\Type\MetaFieldsCollectionType;
|
||||
use App\Form\Type\TagsType;
|
||||
use App\Form\Type\TimePickerType;
|
||||
|
@ -365,6 +366,9 @@ class TimesheetEditForm extends AbstractType
|
|||
])
|
||||
->add('hourlyRate', HourlyRateType::class, [
|
||||
'currency' => $currency,
|
||||
])
|
||||
->add('internalRate', InternalRateType::class, [
|
||||
'currency' => $currency,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,8 +44,12 @@ final class TimesheetToolbarForm extends AbstractType
|
|||
$this->addExportStateChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
$this->addOrder($builder);
|
||||
$this->addOrderBy($builder, TimesheetQuery::TIMESHEET_ORDER_ALLOWED);
|
||||
|
||||
$query = $options['data'];
|
||||
if ($query instanceof TimesheetQuery) {
|
||||
$this->addOrder($builder);
|
||||
$this->addOrderBy($builder, $query->getAllowedOrderColumns());
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
|
|
38
src/Form/Type/InternalRateType.php
Normal file
38
src/Form/Type/InternalRateType.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to set the internal rate.
|
||||
*/
|
||||
final class InternalRateType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
// documentation is for NelmioApiDocBundle
|
||||
'documentation' => [
|
||||
'type' => 'number',
|
||||
'description' => 'Internal (hourly) rate',
|
||||
],
|
||||
'required' => false,
|
||||
'label' => 'internalRate',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return MoneyType::class;
|
||||
}
|
||||
}
|
|
@ -44,6 +44,10 @@ class BaseQuery
|
|||
* @var array<string, string>
|
||||
*/
|
||||
private array $orderGroups = [];
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
private array $allowedOrderColumns = [];
|
||||
private ?User $currentUser = null;
|
||||
/**
|
||||
* @var array<Team>
|
||||
|
@ -428,4 +432,25 @@ class BaseQuery
|
|||
{
|
||||
return $this->isApiCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAllowedOrderColumns(): array
|
||||
{
|
||||
return $this->allowedOrderColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $allowedOrderColumns
|
||||
*/
|
||||
public function setAllowedOrderColumns(array $allowedOrderColumns): void
|
||||
{
|
||||
$this->allowedOrderColumns = $allowedOrderColumns;
|
||||
}
|
||||
|
||||
public function addAllowedOrderColumns(string $allowedOrderColumn): void
|
||||
{
|
||||
$this->allowedOrderColumns[] = $allowedOrderColumn;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,10 @@ class TimesheetQuery extends ActivityQuery implements BillableInterface, DateRan
|
|||
public const STATE_EXPORTED = 4;
|
||||
public const STATE_NOT_EXPORTED = 5;
|
||||
|
||||
public const TIMESHEET_ORDER_ALLOWED = ['begin', 'end', 'duration', 'rate', 'hourlyRate', 'customer', 'project', 'activity', 'description'];
|
||||
/**
|
||||
* @deprecated since 2.31.0
|
||||
*/
|
||||
public const TIMESHEET_ORDER_ALLOWED = ['begin', 'end', 'duration', 'rate', 'hourlyRate', 'customer', 'project', 'activity', 'description', 'billable', 'exported'];
|
||||
|
||||
private ?User $timesheetUser = null;
|
||||
/** @var array<Activity> */
|
||||
|
@ -61,6 +64,7 @@ class TimesheetQuery extends ActivityQuery implements BillableInterface, DateRan
|
|||
'users' => [],
|
||||
'activities' => [],
|
||||
]);
|
||||
$this->setAllowedOrderColumns(self::TIMESHEET_ORDER_ALLOWED); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function addQueryHint(TimesheetQueryHint $hint): void
|
||||
|
|
|
@ -730,11 +730,6 @@ class TimesheetRepository extends EntityRepository
|
|||
->setParameter('begin', \DateTimeImmutable::createFromInterface($startFrom), Types::DATETIME_IMMUTABLE);
|
||||
}
|
||||
|
||||
$qb->join('t.project', 'p');
|
||||
$qb->join('p.customer', 'c');
|
||||
|
||||
$this->addPermissionCriteria($qb, $user);
|
||||
|
||||
$results = $qb->getQuery()->getScalarResult();
|
||||
|
||||
if (empty($results)) {
|
||||
|
|
|
@ -169,6 +169,9 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||
|
||||
public function refreshUser(UserInterface $user): User
|
||||
{
|
||||
// TODO 3.0 add a last_updated field to user and ONLY load this for comparison.
|
||||
// TODO then only execute the below code if last_updated != session.last_updated
|
||||
|
||||
return $this->loadUserByIdentifier($user->getUserIdentifier());
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,19 @@ final class DateTimeFactory
|
|||
return DateTime::createFromInterface($date);
|
||||
}
|
||||
|
||||
private function createDate(DateTimeInterface|string|null $date = null): \DateTimeImmutable
|
||||
{
|
||||
if ($date === null) {
|
||||
$date = 'now';
|
||||
}
|
||||
|
||||
if (\is_string($date)) {
|
||||
return $this->create($date);
|
||||
}
|
||||
|
||||
return \DateTimeImmutable::createFromInterface($date);
|
||||
}
|
||||
|
||||
public function getStartOfWeek(DateTimeInterface|string|null $date = null): DateTime
|
||||
{
|
||||
$date = $this->getDate($date);
|
||||
|
@ -139,6 +152,20 @@ final class DateTimeFactory
|
|||
return new \DateTimeImmutable($datetime, $this->getTimezone());
|
||||
}
|
||||
|
||||
public function createStartOfDay(DateTimeInterface|string|null $date = null): \DateTimeImmutable
|
||||
{
|
||||
$date = $this->createDate($date);
|
||||
|
||||
return $date->modify('00:00:00');
|
||||
}
|
||||
|
||||
public function createEndOfDay(DateTimeInterface|string|null $date = null): \DateTimeImmutable
|
||||
{
|
||||
$date = $this->createDate($date);
|
||||
|
||||
return $date->modify('23:59:59');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $format
|
||||
* @param null|string $datetime
|
||||
|
|
|
@ -191,6 +191,7 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
|
|||
$admin = false;
|
||||
$superAdmin = false;
|
||||
$timezone = date_default_timezone_get();
|
||||
$roles = [];
|
||||
|
||||
if ($user !== null) {
|
||||
$browserTitle = (bool) $user->getPreferenceValue('update_browser_title');
|
||||
|
@ -200,6 +201,7 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
|
|||
$admin = $user->isAdmin();
|
||||
$superAdmin = $user->isSuperAdmin();
|
||||
$timezone = $user->getTimezone();
|
||||
$roles = $user->getRoles();
|
||||
}
|
||||
|
||||
$language ??= $this->locale ?? User::DEFAULT_LANGUAGE;
|
||||
|
@ -213,7 +215,7 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
|
|||
'twentyFourHours' => $this->localeService->is24Hour($this->locale),
|
||||
'updateBrowserTitle' => $browserTitle,
|
||||
'timezone' => $timezone,
|
||||
'user' => ['id' => $id, 'name' => $name, 'admin' => $admin, 'superAdmin' => $superAdmin],
|
||||
'user' => ['id' => $id, 'name' => $name, 'admin' => $admin, 'superAdmin' => $superAdmin, 'roles' => $roles],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,17 @@ final class Duration
|
|||
public const FORMAT_DECIMAL = 'decimal';
|
||||
public const FORMAT_DEFAULT = '%h:%m';
|
||||
|
||||
public function formatDecimal(?int $seconds): float
|
||||
{
|
||||
if ($seconds === null || $seconds === 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return (float) number_format($seconds / 3600, 2, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms seconds into a duration string.
|
||||
*
|
||||
* @param int|null $seconds
|
||||
* @param string $format
|
||||
* @return string|null
|
||||
*/
|
||||
public function format(?int $seconds, string $format = self::FORMAT_DEFAULT): ?string
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ abstract class AbstractWidget implements WidgetInterface
|
|||
|
||||
public function getWidth(): int
|
||||
{
|
||||
return WidgetInterface::WIDTH_SMALL;
|
||||
return WidgetInterface::WIDTH_HALF;
|
||||
}
|
||||
|
||||
public function getPermissions(): array
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
{# what to do here ? #}
|
||||
{% else %}
|
||||
{% if values.children is defined and values.children|length > 0 %}
|
||||
<div class="dropdown">
|
||||
<div class="dropdown{% if values.class is defined %} {{ values.class }}{% endif %}">
|
||||
<button type="button" class="btn {{ btnClasses }} dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ icon(icon, true) }}{% if large %} {{ values.title is defined ? values.title|trans : icon|trans }}{% endif %}
|
||||
</button>
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
{% embed '@theme/embeds/card.html.twig' with {'project': project, 'activities': activities, 'page': page} %}
|
||||
{% embed '@theme/embeds/card.html.twig' with {'project': project, 'activities': activities, 'page': page, 'id': 'activity_list_box'} %}
|
||||
{% import "activity/actions.html.twig" as actions %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block box_title %}{{ 'activities'|trans }}{% endblock %}
|
||||
{% block box_attributes %}
|
||||
id="activity_list_box" data-reload="kimai.activityUpdate kimai.activityDelete"
|
||||
{% endblock %}
|
||||
{% block box_attributes %} data-reload="kimai.activityUpdate kimai.activityDelete" {% endblock %}
|
||||
{% block box_tools %}
|
||||
{%- if project.visible and project.customer.visible and is_granted('create_activity') -%}
|
||||
{{ widgets.card_tool_button('create', {'class': 'modal-ajax-form open-edit', 'title': 'create', 'url': path('admin_activity_create_with_project', {'project': project.id})}) }}
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
{% if form.metaFields is defined and form.metaFields is not empty %}
|
||||
{{ form_row(form.metaFields) }}
|
||||
{% endif %}
|
||||
{% if form.fixedRate is defined or form.hourlyRate is defined or form.billable is defined or form.billableMode is defined or form.exported is defined %}
|
||||
{% if form.fixedRate is defined or form.hourlyRate is defined or form.internalRate is defined or form.billable is defined or form.billableMode is defined or form.exported is defined %}
|
||||
{% embed '@theme/embeds/collapsible.html.twig' with {id: 'timesheet_extended_settings'} %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% block title %}{{ 'extended_settings'|trans }}{% endblock %}
|
||||
|
@ -87,6 +87,9 @@
|
|||
{% if form.hourlyRate is defined %}
|
||||
{{ form_row(form.hourlyRate, {'row_attr': {'class': 'mb-3 ' ~ form.vars.name ~ '_row_' ~ form.hourlyRate.vars.name}}) }}
|
||||
{% endif %}
|
||||
{% if form.internalRate is defined %}
|
||||
{{ form_row(form.internalRate, {'row_attr': {'class': 'mb-3 ' ~ form.vars.name ~ '_row_' ~ form.internalRate.vars.name}}) }}
|
||||
{% endif %}
|
||||
{% if form.billable is defined %}
|
||||
{{ form_row(form.billable, {'row_attr': {'class': 'mb-3 ' ~ form.vars.name ~ '_row_' ~ form.billable.vars.name}}) }}
|
||||
{% elseif form.billableMode is defined %}
|
||||
|
|
|
@ -102,7 +102,8 @@ class CsvRendererTest extends AbstractRendererTestCase
|
|||
'2019-06-16',
|
||||
'12:00',
|
||||
'12:06',
|
||||
'0.11',
|
||||
'0:06',
|
||||
//'0.11',
|
||||
'EUR',
|
||||
'0',
|
||||
'0',
|
||||
|
@ -134,7 +135,8 @@ class CsvRendererTest extends AbstractRendererTestCase
|
|||
'2019-06-16',
|
||||
'12:00',
|
||||
'12:06',
|
||||
'0.11',
|
||||
'0:06',
|
||||
//'0.11',
|
||||
'EUR',
|
||||
'0',
|
||||
'0',
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?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\CellFormatter;
|
||||
|
||||
use App\Export\Package\CellFormatter\DurationDecimalFormatter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Export\Package\CellFormatter\DurationDecimalFormatter
|
||||
*/
|
||||
class DurationDecimalFormatterTest extends TestCase
|
||||
{
|
||||
public function testFormatValueReturnsFormattedDurationForNumericValue(): void
|
||||
{
|
||||
$formatter = new DurationDecimalFormatter();
|
||||
$result = $formatter->formatValue(7200);
|
||||
self::assertEquals(2.00, $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsZeroForNonNumericValue(): void
|
||||
{
|
||||
$formatter = new DurationDecimalFormatter();
|
||||
$result = $formatter->formatValue('not a number');
|
||||
self::assertEquals(0.0, $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForFloatValue(): void
|
||||
{
|
||||
$formatter = new DurationDecimalFormatter();
|
||||
$result = $formatter->formatValue(4500.5);
|
||||
self::assertEquals(1.25, $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForStringValue(): void
|
||||
{
|
||||
$formatter = new DurationDecimalFormatter();
|
||||
$result = $formatter->formatValue('4500.5');
|
||||
self::assertEquals(1.25, $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsZeroForNullValue(): void
|
||||
{
|
||||
$formatter = new DurationDecimalFormatter();
|
||||
$result = $formatter->formatValue(null);
|
||||
self::assertEquals(0.0, $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForNegativeValue(): void
|
||||
{
|
||||
$formatter = new DurationDecimalFormatter();
|
||||
$result = $formatter->formatValue(-3600);
|
||||
self::assertEquals(-1.00, $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForNegativeStringValue(): void
|
||||
{
|
||||
$formatter = new DurationDecimalFormatter();
|
||||
$result = $formatter->formatValue('-3600');
|
||||
self::assertEquals(-1.00, $result);
|
||||
}
|
||||
}
|
|
@ -21,34 +21,34 @@ class DurationFormatterTest extends TestCase
|
|||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$result = $formatter->formatValue(7200);
|
||||
self::assertEquals(2.00, $result);
|
||||
self::assertEquals('2:00', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsZeroForNonNumericValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$result = $formatter->formatValue('not a number');
|
||||
self::assertEquals(0.0, $result);
|
||||
self::assertEquals('0:00', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForFloatValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$result = $formatter->formatValue(4500.5);
|
||||
self::assertEquals(1.25, $result);
|
||||
self::assertEquals('1:15', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsZeroForNullValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$result = $formatter->formatValue(null);
|
||||
self::assertEquals(0.0, $result);
|
||||
self::assertEquals('0:00', $result);
|
||||
}
|
||||
|
||||
public function testFormatValueReturnsFormattedDurationForNegativeValue(): void
|
||||
{
|
||||
$formatter = new DurationFormatter();
|
||||
$result = $formatter->formatValue(-3600);
|
||||
self::assertEquals(-1.00, $result);
|
||||
self::assertEquals('-1:00', $result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -459,6 +459,7 @@ class LocaleFormatExtensionsTest extends TestCase
|
|||
'name' => null,
|
||||
'admin' => false,
|
||||
'superAdmin' => false,
|
||||
'roles' => [],
|
||||
],
|
||||
];
|
||||
$user = $this->createMock(User::class);
|
||||
|
@ -488,6 +489,7 @@ class LocaleFormatExtensionsTest extends TestCase
|
|||
'name' => 'anonymous',
|
||||
'admin' => false,
|
||||
'superAdmin' => false,
|
||||
'roles' => [],
|
||||
],
|
||||
];
|
||||
|
||||
|
|
Loading…
Reference in a new issue