diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index a1a34fa24f..e9ba1b9b27 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -1,5 +1,6 @@
name: Sonarcloud
on:
+ pull_request:
push:
branches:
- main
@@ -12,6 +13,33 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
+
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup PHP with Xdebug
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+ coverage: xdebug
+
+ - name: Install Composer dependencies
+ run: composer install --prefer-dist --no-interaction --no-progress --no-scripts
+
+ - name: Copy environment file
+ run: cp .env.example .env
+
+ - name: Generate app key
+ run: php artisan key:generate
+
+ - name: "Run tests with coverage"
+ run: composer coverage
+
+ - name: Fix code coverage paths
+ run: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' coverage.xml
+
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
diff --git a/.gitignore b/.gitignore
index 220b48d300..ab681f4f44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,4 @@ npm-debug.log
yarn-error.log
.env
/.ci/php-cs-fixer/vendor
-/.ci/coverage
+coverage.xml
diff --git a/app/Support/Calendar/Calculator.php b/app/Support/Calendar/Calculator.php
new file mode 100644
index 0000000000..b4475c7df7
--- /dev/null
+++ b/app/Support/Calendar/Calculator.php
@@ -0,0 +1,81 @@
+.
+ */
+
+namespace FireflyIII\Support\Calendar;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Exceptions\IntervalException;
+
+class Calculator
+{
+ const DEFAULT_INTERVAL = 1;
+ private static array $intervals = [];
+ private static ?\SplObjectStorage $intervalMap = null;
+
+ private static function loadIntervalMap(): \SplObjectStorage
+ {
+ if (self::$intervalMap != null) {
+ return self::$intervalMap;
+ }
+ self::$intervalMap = new \SplObjectStorage();
+ foreach (Periodicity::cases() as $interval) {
+ $periodicityClass = __NAMESPACE__ . "\\Periodicity\\{$interval->name}";
+ self::$intervals[] = $interval->name;
+ self::$intervalMap->attach($interval, new $periodicityClass());
+ }
+ return self::$intervalMap;
+ }
+
+ private static function containsInterval(Periodicity $periodicity): bool
+ {
+ return self::loadIntervalMap()->contains($periodicity);
+ }
+
+ public function isAvailablePeriodicity(Periodicity $periodicity): bool
+ {
+ return self::containsInterval($periodicity);
+ }
+
+ private function skipInterval(int $skip): int
+ {
+ return self::DEFAULT_INTERVAL + $skip;
+ }
+
+ /**
+ * @param Carbon $epoch
+ * @param Periodicity $periodicity
+ * @param int $skipInterval
+ * @return Carbon
+ * @throws IntervalException
+ */
+ public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon
+ {
+ if (!self::isAvailablePeriodicity($periodicity)) {
+ throw IntervalException::unavailable($periodicity, self::$intervals);
+ }
+
+ /** @var Periodicity\Interval $periodicity */
+ $periodicity = self::$intervalMap->offsetGet($periodicity);
+ $interval = $this->skipInterval($skipInterval);
+ return $periodicity->nextDate($epoch->clone(), $interval);
+ }
+
+}
diff --git a/app/Support/Calendar/Exceptions/IntervalException.php b/app/Support/Calendar/Exceptions/IntervalException.php
new file mode 100644
index 0000000000..e30621f5a1
--- /dev/null
+++ b/app/Support/Calendar/Exceptions/IntervalException.php
@@ -0,0 +1,51 @@
+.
+ */
+
+namespace FireflyIII\Support\Calendar\Exceptions;
+
+use FireflyIII\Support\Calendar\Periodicity;
+
+final class IntervalException extends \Exception
+{
+ protected $message = 'The periodicity %s is unknown. Choose one of available periodicity: %s';
+
+ public readonly Periodicity $periodicity;
+ public readonly array $availableIntervals;
+
+ public static function unavailable(
+ Periodicity $periodicity,
+ array $intervals,
+ int $code = 0,
+ ?\Throwable $previous = null
+ ): IntervalException
+ {
+ $message = sprintf(
+ 'The periodicity %s is unknown. Choose one of available periodicity: %s',
+ $periodicity->name,
+ join(', ', $intervals)
+ );
+
+ $exception = new IntervalException($message, $code, $previous);
+ $exception->periodicity = $periodicity;
+ $exception->availableIntervals = $intervals;
+ return $exception;
+ }
+}
diff --git a/app/Support/Calendar/Periodicity.php b/app/Support/Calendar/Periodicity.php
new file mode 100644
index 0000000000..83d5d449f9
--- /dev/null
+++ b/app/Support/Calendar/Periodicity.php
@@ -0,0 +1,34 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar;
+
+enum Periodicity
+{
+ case Daily;
+ case Weekly;
+ case Fortnightly;
+ case Monthly;
+ case Bimonthly;
+ case Quarterly;
+ case HalfYearly;
+ case Yearly;
+}
diff --git a/app/Support/Calendar/Periodicity/Bimonthly.php b/app/Support/Calendar/Periodicity/Bimonthly.php
new file mode 100644
index 0000000000..b521813bc1
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Bimonthly.php
@@ -0,0 +1,27 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+final class Bimonthly extends Monthly
+{
+ const INTERVAL = 2;
+}
diff --git a/app/Support/Calendar/Periodicity/Daily.php b/app/Support/Calendar/Periodicity/Daily.php
new file mode 100644
index 0000000000..1ca47c983a
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Daily.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+
+final class Daily extends Interval
+{
+ public function nextDate(Carbon $date, int $interval = 1): Carbon
+ {
+ return ($date->clone())->addDays($this->skip($interval));
+ }
+}
diff --git a/app/Support/Calendar/Periodicity/Fortnightly.php b/app/Support/Calendar/Periodicity/Fortnightly.php
new file mode 100644
index 0000000000..cd9af54441
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Fortnightly.php
@@ -0,0 +1,27 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+final class Fortnightly extends Weekly
+{
+ const INTERVAL = 2;
+}
diff --git a/app/Support/Calendar/Periodicity/HalfYearly.php b/app/Support/Calendar/Periodicity/HalfYearly.php
new file mode 100644
index 0000000000..f5bb2f0308
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/HalfYearly.php
@@ -0,0 +1,27 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+final class HalfYearly extends Monthly
+{
+ const INTERVAL = 6;
+}
diff --git a/app/Support/Calendar/Periodicity/Interspacable.php b/app/Support/Calendar/Periodicity/Interspacable.php
new file mode 100644
index 0000000000..e61f360dff
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Interspacable.php
@@ -0,0 +1,29 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+
+interface Interspacable
+{
+ public function nextDate(Carbon $date, int $interval = 1): Carbon;
+}
diff --git a/app/Support/Calendar/Periodicity/Interval.php b/app/Support/Calendar/Periodicity/Interval.php
new file mode 100644
index 0000000000..1e40ef27b8
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Interval.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+abstract class Interval implements Interspacable
+{
+ const INTERVAL = 1;
+
+ public function skip(int $skip): int
+ {
+ return static::INTERVAL * $skip;
+ }
+}
diff --git a/app/Support/Calendar/Periodicity/Monthly.php b/app/Support/Calendar/Periodicity/Monthly.php
new file mode 100644
index 0000000000..e1739a643a
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Monthly.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+
+class Monthly extends Interval
+{
+ public function nextDate(Carbon $date, int $interval = 1): Carbon
+ {
+ return ($date->clone())->addMonthsNoOverflow($this->skip($interval));
+ }
+}
diff --git a/app/Support/Calendar/Periodicity/Quarterly.php b/app/Support/Calendar/Periodicity/Quarterly.php
new file mode 100644
index 0000000000..46bac74728
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Quarterly.php
@@ -0,0 +1,27 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+final class Quarterly extends Monthly
+{
+ const INTERVAL = 3;
+}
diff --git a/app/Support/Calendar/Periodicity/Weekly.php b/app/Support/Calendar/Periodicity/Weekly.php
new file mode 100644
index 0000000000..fa0724cefc
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Weekly.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+
+class Weekly extends Interval
+{
+ public function nextDate(Carbon $date, int $interval = 1): Carbon
+ {
+ return ($date->clone())->addWeeks($this->skip($interval));
+ }
+}
diff --git a/app/Support/Calendar/Periodicity/Yearly.php b/app/Support/Calendar/Periodicity/Yearly.php
new file mode 100644
index 0000000000..466cb19640
--- /dev/null
+++ b/app/Support/Calendar/Periodicity/Yearly.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace FireflyIII\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+
+final class Yearly extends Interval
+{
+ public function nextDate(Carbon $date, int $interval = 1): Carbon
+ {
+ return ($date->clone())->addYearsNoOverflow($this->skip($interval));
+ }
+}
diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php
index 7d5f90b23b..f8e435c675 100644
--- a/app/Support/Navigation.php
+++ b/app/Support/Navigation.php
@@ -26,6 +26,9 @@ namespace FireflyIII\Support;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Fiscal\FiscalHelperInterface;
+use FireflyIII\Support\Calendar\Calculator;
+use FireflyIII\Support\Calendar\Exceptions\IntervalException;
+use FireflyIII\Support\Calendar\Periodicity;
use Illuminate\Support\Facades\Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
@@ -35,81 +38,88 @@ use Psr\Container\NotFoundExceptionInterface;
*/
class Navigation
{
+ private Calculator $calculator;
+
+ public function __construct(Calculator $calculator = null)
+ {
+ $this->calculator = ($calculator instanceof Calculator) ?: new Calculator();
+ }
+
+ /**
+ * @param Carbon $epoch
+ * @param Periodicity $periodicity
+ * @param int $skipInterval
+ * @return Carbon
+ */
+ public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon
+ {
+ try {
+ return $this->calculator->nextDateByInterval($epoch, $periodicity, $skipInterval);
+ } catch (IntervalException $exception) {
+ Log::warning($exception->getMessage(), ['exception' => $exception]);
+ } catch (\Throwable $exception) {
+ Log::error($exception->getMessage(), ['exception' => $exception]);
+ }
+
+ Log::debug(
+ "Any error occurred to calculate the next date.",
+ ['date' => $epoch, 'periodicity' => $periodicity->name, 'skipInterval' => $skipInterval]
+ );
+
+ return $epoch;
+ }
+
/**
* @param Carbon $theDate
* @param string $repeatFreq
* @param int $skip
*
* @return Carbon
+ * @deprecated This method will be substituted by nextDateByInterval()
*/
- public function addPeriod(Carbon $theDate, string $repeatFreq, int $skip): Carbon
+ public function addPeriod(Carbon $theDate, string $repeatFreq, int $skip = 0): Carbon
{
$date = clone $theDate;
$add = ($skip + 1);
$functionMap = [
- '1D' => 'addDays',
- 'daily' => 'addDays',
- '1W' => 'addWeeks',
- 'weekly' => 'addWeeks',
- 'week' => 'addWeeks',
- '1M' => 'addMonths',
- 'month' => 'addMonths',
- 'monthly' => 'addMonths',
- '3M' => 'addMonths',
- 'quarter' => 'addMonths',
- 'quarterly' => 'addMonths',
- '6M' => 'addMonths',
- 'half-year' => 'addMonths',
- 'year' => 'addYears',
- 'yearly' => 'addYears',
- '1Y' => 'addYears',
- 'custom' => 'addMonths', // custom? just add one month.
+ '1D' => Periodicity::Daily,
+ 'daily' => Periodicity::Daily,
+ '1W' => Periodicity::Weekly,
+ 'weekly' => Periodicity::Weekly,
+ 'week' => Periodicity::Weekly,
+ '1M' => Periodicity::Monthly,
+ 'month' => Periodicity::Monthly,
+ 'monthly' => Periodicity::Monthly,
+ '3M' => Periodicity::Quarterly,
+ 'quarter' => Periodicity::Quarterly,
+ 'quarterly' => Periodicity::Quarterly,
+ '6M' => Periodicity::HalfYearly,
+ 'half-year' => Periodicity::HalfYearly,
+ 'year' => Periodicity::Yearly,
+ 'yearly' => Periodicity::Yearly,
+ '1Y' => Periodicity::Yearly,
+ 'custom' => Periodicity::Monthly, // custom? just add one month.
// last X periods? Jump the relevant month / quarter / year
- 'last7' => 'addDays',
- 'last30' => 'addMonths',
- 'last90' => 'addMonths',
- 'last365' => 'addYears',
- 'MTD' => 'addMonths',
- 'QTD' => 'addMonths',
- 'YTD' => 'addYears',
- ];
- $modifierMap = [
- 'quarter' => 3,
- '3M' => 3,
- 'quarterly' => 3,
- '6M' => 6,
- 'half-year' => 6,
- 'last7' => 7,
- 'last90' => 3,
- 'QTD' => 3,
+ 'last7' => Periodicity::Weekly,
+ 'last30' => Periodicity::Monthly,
+ 'last90' => Periodicity::Quarterly,
+ 'last365' => Periodicity::Yearly,
+ 'MTD' => Periodicity::Monthly,
+ 'QTD' => Periodicity::Quarterly,
+ 'YTD' => Periodicity::Yearly,
];
if (!array_key_exists($repeatFreq, $functionMap)) {
- Log::error(sprintf('Cannot do addPeriod for $repeat_freq "%s"', $repeatFreq));
-
+ Log::error(sprintf(
+ 'The periodicity %s is unknown. Choose one of available periodicity: %s',
+ $repeatFreq,
+ join(', ', array_keys($functionMap))
+ ));
return $theDate;
}
- if (array_key_exists($repeatFreq, $modifierMap)) {
- $add *= $modifierMap[$repeatFreq];
- }
- $function = $functionMap[$repeatFreq];
- $date->$function($add);
- // if period is 1M and diff in month is 2 and new DOM > 1, sub a number of days:
- // AND skip is 1
- // result is:
- // '2019-01-29', '2019-02-28'
- // '2019-01-30', '2019-02-28'
- // '2019-01-31', '2019-02-28'
-
- $months = ['1M', 'month', 'monthly'];
- $difference = $date->month - $theDate->month;
- if (1 === $add && 2 === $difference && $date->day > 0 && in_array($repeatFreq, $months, true)) {
- $date->subDays($date->day);
- }
-
- return $date;
+ return $this->nextDateByInterval($theDate, $functionMap[$repeatFreq], $skip);
}
/**
diff --git a/composer.json b/composer.json
index b05a6a12c4..1c6c62f1ba 100644
--- a/composer.json
+++ b/composer.json
@@ -168,6 +168,15 @@
"post-install-cmd": [
"@php artisan firefly:instructions install",
"@php artisan firefly-iii:verify-security-alerts"
+ ],
+ "unit-test": [
+ "@php vendor/bin/phpunit -c phpunit.xml --testsuite unit --no-coverage"
+ ],
+ "integration-test": [
+ "@php vendor/bin/phpunit -c phpunit.xml --testsuite integration --no-coverage"
+ ],
+ "coverage": [
+ "@php vendor/bin/phpunit -c phpunit.xml --testsuite unit"
]
},
"config": {
diff --git a/phpunit.xml b/phpunit.xml
index bdcc61af15..aa51a02e21 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -18,22 +18,46 @@
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see .
-->
-
-
-
-
- ./tests
-
-
-
-
-
-
-
-
-
-
- ./app
-
-
+
+
+
+
+
+
+
+
+
+
+ ./tests
+
+
+ ./tests/unit
+
+
+ ./tests/integration
+
+
+
+
+ app
+
+
+
+
+
+
+
diff --git a/sonar-project.properties b/sonar-project.properties
index 72777406a0..30a426dd31 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -15,3 +15,4 @@ sonar.organization=firefly-iii
sonar.projectVersion=6.0.11
sonar.sources=app,bootstrap,database,resources/assets,resources/views,routes,tests
sonar.sourceEncoding=UTF-8
+sonar.php.coverage.reportPaths=coverage.xml
diff --git a/tests/Api/Autocomplete/AccountControllerTest.php b/tests/integration/Api/Autocomplete/AccountControllerTest.php
similarity index 95%
rename from tests/Api/Autocomplete/AccountControllerTest.php
rename to tests/integration/Api/Autocomplete/AccountControllerTest.php
index 7ff8e51672..5eb0ec0ca7 100644
--- a/tests/Api/Autocomplete/AccountControllerTest.php
+++ b/tests/integration/Api/Autocomplete/AccountControllerTest.php
@@ -21,11 +21,11 @@
declare(strict_types=1);
-namespace Tests\Api\Autocomplete;
+namespace Tests\integration\Api\Autocomplete;
use Laravel\Passport\Passport;
use Log;
-use Tests\TestCase;
+use Tests\integration\TestCase;
/**
* Class AccountControllerTest
diff --git a/tests/CreatesApplication.php b/tests/integration/CreatesApplication.php
similarity index 92%
rename from tests/CreatesApplication.php
rename to tests/integration/CreatesApplication.php
index 22211a5905..6dbc977700 100644
--- a/tests/CreatesApplication.php
+++ b/tests/integration/CreatesApplication.php
@@ -20,7 +20,7 @@
*/
declare(strict_types=1);
-namespace Tests;
+namespace Tests\integration;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Foundation\Application;
@@ -38,7 +38,7 @@ trait CreatesApplication
*/
public function createApplication()
{
- $app = require __DIR__ . '/../bootstrap/app.php';
+ $app = require_once __DIR__ . '/../../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
diff --git a/tests/TestCase.php b/tests/integration/TestCase.php
similarity index 95%
rename from tests/TestCase.php
rename to tests/integration/TestCase.php
index 4e53124582..e0990a65b6 100644
--- a/tests/TestCase.php
+++ b/tests/integration/TestCase.php
@@ -21,10 +21,10 @@
*/
declare(strict_types=1);
-namespace Tests;
+namespace Tests\integration;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
-use Tests\Traits\CollectsValues;
+use Tests\integration\Traits\CollectsValues;
/**
* Class TestCase
diff --git a/tests/Traits/CollectsValues.php b/tests/integration/Traits/CollectsValues.php
similarity index 96%
rename from tests/Traits/CollectsValues.php
rename to tests/integration/Traits/CollectsValues.php
index 33c6bfe647..cae0305ff3 100644
--- a/tests/Traits/CollectsValues.php
+++ b/tests/integration/Traits/CollectsValues.php
@@ -21,7 +21,7 @@
declare(strict_types=1);
-namespace Tests\Traits;
+namespace Tests\integration\Traits;
use FireflyIII\User;
diff --git a/tests/unit/Support/Calendar/CalculatorProvider.php b/tests/unit/Support/Calendar/CalculatorProvider.php
new file mode 100644
index 0000000000..800adb15b0
--- /dev/null
+++ b/tests/unit/Support/Calendar/CalculatorProvider.php
@@ -0,0 +1,131 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use Tests\unit\Support\Calendar\Periodicity\IntervalProvider;
+
+readonly class CalculatorProvider
+{
+ public IntervalProvider $intervalProvider;
+ public Periodicity $periodicity;
+ public string $label;
+ public int $skip;
+
+ private function __construct(IntervalProvider $intervalProvider, Periodicity $periodicity, int $skip = 0)
+ {
+ $this->skip = $skip;
+ $this->intervalProvider = $intervalProvider;
+ $this->periodicity = $periodicity;
+ $this->label = "{$periodicity->name} {$intervalProvider->label}";
+ }
+
+ public static function from(Periodicity $periodicity, IntervalProvider $interval, int $skip = 0): CalculatorProvider
+ {
+ return new self($interval, $periodicity, $skip);
+ }
+
+ public function epoch(): Carbon
+ {
+ return $this->intervalProvider->epoch;
+ }
+
+ public function expected(): Carbon
+ {
+ return $this->intervalProvider->expected;
+ }
+
+ public static function providePeriodicityWithSkippedIntervals(): \Generator
+ {
+ $intervals = [
+ CalculatorProvider::from(Periodicity::Daily, new IntervalProvider(Carbon::now(), Carbon::now()->addDays(2)), 1),
+ CalculatorProvider::from(Periodicity::Daily, new IntervalProvider(Carbon::now(), Carbon::now()->addDays(3)), 2),
+ CalculatorProvider::from(Periodicity::Daily, new IntervalProvider(Carbon::parse('2023-01-31'), Carbon::parse('2023-02-11')), 10),
+
+ CalculatorProvider::from(Periodicity::Weekly, new IntervalProvider(Carbon::now(), Carbon::now()->addWeeks(3)), 2),
+ CalculatorProvider::from(Periodicity::Weekly, new IntervalProvider(Carbon::parse('2023-01-31'), Carbon::parse('2023-02-14')), 1),
+
+ CalculatorProvider::from(Periodicity::Fortnightly, new IntervalProvider(Carbon::now(), Carbon::now()->addWeeks(4)), 1),
+ CalculatorProvider::from(Periodicity::Fortnightly, new IntervalProvider(Carbon::parse('2023-01-29'), Carbon::parse('2023-02-26')), 1),
+ CalculatorProvider::from(Periodicity::Fortnightly, new IntervalProvider(Carbon::parse('2023-01-30'), Carbon::parse('2023-02-27')), 1),
+ CalculatorProvider::from(Periodicity::Fortnightly, new IntervalProvider(Carbon::parse('2023-01-31'), Carbon::parse('2023-02-28')), 1),
+
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::now(), Carbon::now()->addMonthsNoOverflow(2)), 1),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2019-12-30'), Carbon::parse('2020-02-29')), 1),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2019-12-31'), Carbon::parse('2020-02-29')), 1),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2020-01-29'), Carbon::parse('2020-03-29')), 1),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2020-01-31'), Carbon::parse('2020-09-30')), 7),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2020-12-29'), Carbon::parse('2021-02-28')), 1),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2020-12-30'), Carbon::parse('2021-02-28')), 1),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2020-12-31'), Carbon::parse('2021-02-28')), 1),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2023-03-31'), Carbon::parse('2023-11-30')), 7),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2023-05-31'), Carbon::parse('2023-08-31')), 2),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2023-07-31'), Carbon::parse('2023-09-30')), 1),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2023-10-30'), Carbon::parse('2024-02-29')), 3),
+ CalculatorProvider::from(Periodicity::Monthly, new IntervalProvider(Carbon::parse('2023-10-31'), Carbon::parse('2024-02-29')), 3),
+
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::now(), Carbon::now()->addMonthsNoOverflow(6)), 2),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2019-08-29'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2019-08-30'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2019-08-31'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2019-10-30'), Carbon::parse('2020-02-29')), 1),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2019-10-31'), Carbon::parse('2020-02-29')), 1),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2020-02-29'), Carbon::parse('2021-02-28')), 5),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2020-08-29'), Carbon::parse('2021-02-28')), 2),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2020-08-30'), Carbon::parse('2021-02-28')), 2),
+ CalculatorProvider::from(Periodicity::Bimonthly, new IntervalProvider(Carbon::parse('2020-08-31'), Carbon::parse('2021-02-28')), 2),
+
+ CalculatorProvider::from(Periodicity::Quarterly, new IntervalProvider(Carbon::now(), Carbon::now()->addMonthsNoOverflow(9)), 2),
+ CalculatorProvider::from(Periodicity::Quarterly, new IntervalProvider(Carbon::parse('2019-05-29'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::Quarterly, new IntervalProvider(Carbon::parse('2019-05-30'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::Quarterly, new IntervalProvider(Carbon::parse('2019-05-31'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::Quarterly, new IntervalProvider(Carbon::parse('2020-02-29'), Carbon::parse('2021-02-28')), 3),
+ CalculatorProvider::from(Periodicity::Quarterly, new IntervalProvider(Carbon::parse('2020-08-29'), Carbon::parse('2021-02-28')), 1),
+ CalculatorProvider::from(Periodicity::Quarterly, new IntervalProvider(Carbon::parse('2020-08-30'), Carbon::parse('2021-02-28')), 1),
+ CalculatorProvider::from(Periodicity::Quarterly, new IntervalProvider(Carbon::parse('2020-08-31'), Carbon::parse('2021-02-28')), 1),
+
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::now(), Carbon::now()->addMonthsNoOverflow(12)), 1),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::now(), Carbon::now()->addMonthsNoOverflow(18)), 2),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::now(), Carbon::now()->addMonthsNoOverflow(24)), 3),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2018-08-29'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2018-08-30'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2018-08-31'), Carbon::parse('2020-02-29')), 2),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2019-01-31'), Carbon::parse('2021-01-31')), 3),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2019-02-28'), Carbon::parse('2021-08-28')), 4),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2020-01-31'), Carbon::parse('2021-01-31')), 1),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2020-02-29'), Carbon::parse('2021-02-28')), 1),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2020-08-29'), Carbon::parse('2022-02-28')), 2),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2020-08-30'), Carbon::parse('2022-02-28')), 2),
+ CalculatorProvider::from(Periodicity::HalfYearly, new IntervalProvider(Carbon::parse('2020-08-31'), Carbon::parse('2022-02-28')), 2),
+
+ CalculatorProvider::from(Periodicity::Yearly, new IntervalProvider(Carbon::now(), Carbon::now()->addYearsNoOverflow(3)), 2),
+ CalculatorProvider::from(Periodicity::Yearly, new IntervalProvider(Carbon::parse('2019-01-29'), Carbon::parse('2025-01-29')), 5),
+ CalculatorProvider::from(Periodicity::Yearly, new IntervalProvider(Carbon::parse('2020-02-29'), Carbon::parse('2031-02-28')), 10),
+ ];
+
+ /** @var IntervalProvider $interval */
+ foreach ($intervals as $index => $interval) {
+ yield "#{$index} {$interval->label}" => [$interval];
+ }
+ }
+}
diff --git a/tests/unit/Support/Calendar/CalculatorTest.php b/tests/unit/Support/Calendar/CalculatorTest.php
new file mode 100644
index 0000000000..29662db573
--- /dev/null
+++ b/tests/unit/Support/Calendar/CalculatorTest.php
@@ -0,0 +1,102 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar;
+
+use FireflyIII\Support\Calendar\Calculator;
+use FireflyIII\Support\Calendar\Exceptions\IntervalException;
+use FireflyIII\Support\Calendar\Periodicity;
+use PHPUnit\Framework\TestCase;
+use Tests\unit\Support\Calendar\Periodicity\BimonthlyTest;
+use Tests\unit\Support\Calendar\Periodicity\DailyTest;
+use Tests\unit\Support\Calendar\Periodicity\FortnightlyTest;
+use Tests\unit\Support\Calendar\Periodicity\HalfYearlyTest;
+use Tests\unit\Support\Calendar\Periodicity\IntervalProvider;
+use Tests\unit\Support\Calendar\Periodicity\MonthlyTest;
+use Tests\unit\Support\Calendar\Periodicity\QuarterlyTest;
+use Tests\unit\Support\Calendar\Periodicity\WeeklyTest;
+use Tests\unit\Support\Calendar\Periodicity\YearlyTest;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group calculator
+ */
+class CalculatorTest extends TestCase
+{
+ private static function convert(Periodicity $periodicity, array $intervals): array
+ {
+ $periodicityIntervals = [];
+ /** @var IntervalProvider $interval */
+ foreach ($intervals as $index => $interval) {
+ $calculator = CalculatorProvider::from($periodicity, $interval);
+
+ $periodicityIntervals["#{$index} {$calculator->label}"] = [$calculator];
+ }
+ return $periodicityIntervals;
+ }
+
+ public static function provideAllPeriodicity(): \Generator
+ {
+ $intervals = [];
+ $intervals = array_merge($intervals, self::convert(Periodicity::Daily, DailyTest::provideIntervals()));
+ $intervals = array_merge($intervals, self::convert(Periodicity::Weekly, WeeklyTest::provideIntervals()));
+ $intervals = array_merge($intervals, self::convert(Periodicity::Fortnightly, FortnightlyTest::provideIntervals()));
+ $intervals = array_merge($intervals, self::convert(Periodicity::Monthly, MonthlyTest::provideIntervals()));
+ $intervals = array_merge($intervals, self::convert(Periodicity::Bimonthly, BimonthlyTest::provideIntervals()));
+ $intervals = array_merge($intervals, self::convert(Periodicity::Quarterly, QuarterlyTest::provideIntervals()));
+ $intervals = array_merge($intervals, self::convert(Periodicity::HalfYearly, HalfYearlyTest::provideIntervals()));
+ $intervals = array_merge($intervals, self::convert(Periodicity::Yearly, YearlyTest::provideIntervals()));
+
+ /** @var IntervalProvider $interval */
+ foreach ($intervals as $label => $interval) {
+ yield $label => $interval;
+ }
+ }
+
+ /**
+ * @dataProvider provideAllPeriodicity
+ * @throws IntervalException
+ */
+ public function testGivenADailyPeriodicityWhenCallTheNextDateByIntervalMethodThenReturnsTheExpectedDateSuccessful(CalculatorProvider $provider)
+ {
+ $calculator = new Calculator();
+ $period = $calculator->nextDateByInterval($provider->epoch(), $provider->periodicity);
+ $this->assertEquals($provider->expected()->toDateString(), $period->toDateString());
+ }
+
+ public static function provideSkippedIntervals(): \Generator
+ {
+ return CalculatorProvider::providePeriodicityWithSkippedIntervals();
+ }
+
+ /**
+ * @dataProvider provideSkippedIntervals
+ * @throws IntervalException
+ */
+ public function testGivenAnEpochWithSkipIntervalNumberWhenCallTheNextDateBySkippedIntervalMethodThenReturnsTheExpectedDateSuccessful(CalculatorProvider $provider)
+ {
+ $calculator = new Calculator();
+ $period = $calculator->nextDateByInterval($provider->epoch(), $provider->periodicity, $provider->skip);
+ $this->assertEquals($provider->expected()->toDateString(), $period->toDateString());
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/BimonthlyTest.php b/tests/unit/Support/Calendar/Periodicity/BimonthlyTest.php
new file mode 100644
index 0000000000..3bcd6977c5
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/BimonthlyTest.php
@@ -0,0 +1,56 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group periodicity
+ */
+class BimonthlyTest extends IntervalTestCase
+{
+ public static function factory(): Interval
+ {
+ return new Periodicity\Bimonthly();
+ }
+
+ public static function provideIntervals(): array
+ {
+ return [
+ new IntervalProvider(Carbon::now(), Carbon::now()->addMonths(2)),
+ new IntervalProvider(Carbon::parse('2019-01-29'), Carbon::parse('2019-03-29')),
+ new IntervalProvider(Carbon::parse('2018-12-30'), Carbon::parse('2019-02-28')),
+ new IntervalProvider(Carbon::parse('2018-12-31'), Carbon::parse('2019-02-28')),
+ new IntervalProvider(Carbon::parse('2018-11-01'), Carbon::parse('2019-01-01')),
+ new IntervalProvider(Carbon::parse('2019-11-29'), Carbon::parse('2020-01-29')),
+ new IntervalProvider(Carbon::parse('2019-11-30'), Carbon::parse('2020-01-30')),
+ new IntervalProvider(Carbon::parse('2020-12-29'), Carbon::parse('2021-02-28')),
+ new IntervalProvider(Carbon::parse('2020-12-30'), Carbon::parse('2021-02-28')),
+ new IntervalProvider(Carbon::parse('2020-12-31'), Carbon::parse('2021-02-28')),
+ ];
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/DailyTest.php b/tests/unit/Support/Calendar/Periodicity/DailyTest.php
new file mode 100644
index 0000000000..c1175891d1
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/DailyTest.php
@@ -0,0 +1,48 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group periodicity
+ */
+class DailyTest extends IntervalTestCase
+{
+ public static function factory(): Interval
+ {
+ return new Periodicity\Daily();
+ }
+
+ public static function provideIntervals(): array
+ {
+ return [
+ new IntervalProvider(Carbon::now(), Carbon::tomorrow()),
+ new IntervalProvider(Carbon::parse('2023-01-31'), Carbon::parse('2023-02-01')),
+ ];
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/FortnightlyTest.php b/tests/unit/Support/Calendar/Periodicity/FortnightlyTest.php
new file mode 100644
index 0000000000..3c2977074c
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/FortnightlyTest.php
@@ -0,0 +1,48 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group periodicity
+ */
+class FortnightlyTest extends IntervalTestCase
+{
+ public static function factory(): Interval
+ {
+ return new Periodicity\Fortnightly();
+ }
+
+ public static function provideIntervals(): array
+ {
+ return [
+ new IntervalProvider(Carbon::now(), Carbon::now()->addWeeks(2)),
+ new IntervalProvider(Carbon::parse('2023-01-31'), Carbon::parse('2023-02-14')),
+ ];
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/HalfYearlyTest.php b/tests/unit/Support/Calendar/Periodicity/HalfYearlyTest.php
new file mode 100644
index 0000000000..b12d6410e5
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/HalfYearlyTest.php
@@ -0,0 +1,56 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group periodicity
+ */
+class HalfYearlyTest extends IntervalTestCase
+{
+ public static function factory(): Interval
+ {
+ return new Periodicity\HalfYearly();
+ }
+
+ public static function provideIntervals(): array
+ {
+ return [
+ new IntervalProvider(Carbon::now(), Carbon::now()->addMonthsNoOverflow(6)),
+ new IntervalProvider(Carbon::parse('2019-01-29'), Carbon::parse('2019-07-29')),
+ new IntervalProvider(Carbon::parse('2019-01-30'), Carbon::parse('2019-07-30')),
+ new IntervalProvider(Carbon::parse('2019-01-31'), Carbon::parse('2019-07-31')),
+ new IntervalProvider(Carbon::parse('2018-11-01'), Carbon::parse('2019-05-01')),
+ new IntervalProvider(Carbon::parse('2019-08-29'), Carbon::parse('2020-02-29')),
+ new IntervalProvider(Carbon::parse('2019-08-30'), Carbon::parse('2020-02-29')),
+ new IntervalProvider(Carbon::parse('2019-08-31'), Carbon::parse('2020-02-29')),
+ new IntervalProvider(Carbon::parse('2020-08-29'), Carbon::parse('2021-02-28')),
+ new IntervalProvider(Carbon::parse('2020-08-30'), Carbon::parse('2021-02-28')),
+ ];
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/IntervalProvider.php b/tests/unit/Support/Calendar/Periodicity/IntervalProvider.php
new file mode 100644
index 0000000000..88e3cd63ae
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/IntervalProvider.php
@@ -0,0 +1,38 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+
+readonly class IntervalProvider
+{
+ public Carbon $epoch;
+ public Carbon $expected;
+ public string $label;
+
+ public function __construct(Carbon $epoch, Carbon $expected)
+ {
+ $this->epoch = $epoch;
+ $this->expected = $expected;
+ $this->label = "given {$epoch->toDateString()} expects {$expected->toDateString()}";
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/IntervalTestCase.php b/tests/unit/Support/Calendar/Periodicity/IntervalTestCase.php
new file mode 100644
index 0000000000..de3a640fd9
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/IntervalTestCase.php
@@ -0,0 +1,52 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+use PHPUnit\Framework\TestCase;
+
+abstract class IntervalTestCase extends TestCase
+{
+ abstract public static function factory(): Interval;
+
+ public abstract static function provideIntervals(): array;
+
+ public static function provider(): \Generator
+ {
+ $intervals = static::provideIntervals();
+ /** @var IntervalProvider $interval */
+ foreach ($intervals as $interval) {
+ yield "{$interval->label}" => [$interval];
+ }
+ }
+
+ /**
+ * @dataProvider provider
+ * @param IntervalProvider $provider
+ * @return void
+ */
+ public function testGivenAnEpochWhenCallTheNextDateThenReturnsTheExpectedDateSuccessful(IntervalProvider $provider): void
+ {
+ $period = static::factory()->nextDate($provider->epoch);
+ $this->assertEquals($provider->expected->toDateString(), $period->toDateString());
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/MonthlyTest.php b/tests/unit/Support/Calendar/Periodicity/MonthlyTest.php
new file mode 100644
index 0000000000..c7235729c3
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/MonthlyTest.php
@@ -0,0 +1,58 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group periodicity
+ */
+class MonthlyTest extends IntervalTestCase
+{
+ public static function factory(): Interval
+ {
+ return new Periodicity\Monthly();
+ }
+
+ public static function provideIntervals(): array
+ {
+ return [
+ new IntervalProvider(Carbon::now(), Carbon::now()->addMonth(1)),
+ new IntervalProvider(Carbon::parse('2019-01-01'), Carbon::parse('2019-02-01')),
+ new IntervalProvider(Carbon::parse('2020-01-29'), Carbon::parse('2020-02-29')),
+ new IntervalProvider(Carbon::parse('2020-01-30'), Carbon::parse('2020-02-29')),
+ new IntervalProvider(Carbon::parse('2020-01-31'), Carbon::parse('2020-02-29')),
+ new IntervalProvider(Carbon::parse('2021-01-29'), Carbon::parse('2021-02-28')),
+ new IntervalProvider(Carbon::parse('2021-01-30'), Carbon::parse('2021-02-28')),
+ new IntervalProvider(Carbon::parse('2021-01-31'), Carbon::parse('2021-02-28')),
+ new IntervalProvider(Carbon::parse('2023-03-31'), Carbon::parse('2023-04-30')),
+ new IntervalProvider(Carbon::parse('2023-05-31'), Carbon::parse('2023-06-30')),
+ new IntervalProvider(Carbon::parse('2023-08-31'), Carbon::parse('2023-09-30')),
+ new IntervalProvider(Carbon::parse('2023-10-31'), Carbon::parse('2023-11-30')),
+ ];
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/QuarterlyTest.php b/tests/unit/Support/Calendar/Periodicity/QuarterlyTest.php
new file mode 100644
index 0000000000..60e6eba15c
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/QuarterlyTest.php
@@ -0,0 +1,55 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group periodicity
+ */
+class QuarterlyTest extends IntervalTestCase
+{
+ public static function factory(): Interval
+ {
+ return new Periodicity\Quarterly();
+ }
+
+ public static function provideIntervals(): array
+ {
+ return [
+ new IntervalProvider(Carbon::now(), Carbon::now()->addMonths(3)),
+ new IntervalProvider(Carbon::parse('2019-01-29'), Carbon::parse('2019-04-29')),
+ new IntervalProvider(Carbon::parse('2019-01-30'), Carbon::parse('2019-04-30')),
+ new IntervalProvider(Carbon::parse('2019-01-31'), Carbon::parse('2019-04-30')),
+ new IntervalProvider(Carbon::parse('2018-11-01'), Carbon::parse('2019-02-01')),
+ new IntervalProvider(Carbon::parse('2019-11-29'), Carbon::parse('2020-02-29')),
+ new IntervalProvider(Carbon::parse('2019-11-30'), Carbon::parse('2020-02-29')),
+ new IntervalProvider(Carbon::parse('2020-11-29'), Carbon::parse('2021-02-28')),
+ new IntervalProvider(Carbon::parse('2020-11-30'), Carbon::parse('2021-02-28')),
+ ];
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/WeeklyTest.php b/tests/unit/Support/Calendar/Periodicity/WeeklyTest.php
new file mode 100644
index 0000000000..598886b52a
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/WeeklyTest.php
@@ -0,0 +1,48 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group periodicity
+ */
+class WeeklyTest extends IntervalTestCase
+{
+ public static function factory(): Interval
+ {
+ return new Periodicity\Weekly();
+ }
+
+ public static function provideIntervals(): array
+ {
+ return [
+ new IntervalProvider(Carbon::now(), Carbon::now()->addWeek()),
+ new IntervalProvider(Carbon::parse('2023-01-31'), Carbon::parse('2023-02-07')),
+ ];
+ }
+}
diff --git a/tests/unit/Support/Calendar/Periodicity/YearlyTest.php b/tests/unit/Support/Calendar/Periodicity/YearlyTest.php
new file mode 100644
index 0000000000..462f4ceaaa
--- /dev/null
+++ b/tests/unit/Support/Calendar/Periodicity/YearlyTest.php
@@ -0,0 +1,49 @@
+
+ *
+ * This file is part of Firefly III (https://github.com/firefly-iii).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Tests\unit\Support\Calendar\Periodicity;
+
+use Carbon\Carbon;
+use FireflyIII\Support\Calendar\Periodicity;
+use FireflyIII\Support\Calendar\Periodicity\Interval;
+
+/**
+ * @group unit-test
+ * @group support
+ * @group calendar
+ * @group periodicity
+ */
+class YearlyTest extends IntervalTestCase
+{
+ public static function factory(): Interval
+ {
+ return new Periodicity\Yearly();
+ }
+
+ public static function provideIntervals(): array
+ {
+ return [
+ new IntervalProvider(Carbon::now(), Carbon::now()->addYears(1)),
+ new IntervalProvider(Carbon::parse('2019-01-29'), Carbon::parse('2020-01-29')),
+ new IntervalProvider(Carbon::parse('2020-02-29'), Carbon::parse('2021-02-28')),
+ ];
+ }
+}
diff --git a/tests/unit/Support/NavigationTest.php b/tests/unit/Support/NavigationTest.php
new file mode 100644
index 0000000000..6ad2a395dd
--- /dev/null
+++ b/tests/unit/Support/NavigationTest.php
@@ -0,0 +1,172 @@
+navigation = new Navigation();
+ }
+
+ public static function providePeriods(): array
+ {
+ return [
+ '1D' => ['frequency' => '1D', 'from' => Carbon::now(), 'expected' => Carbon::tomorrow()],
+ 'daily' => ['frequency' => 'daily', 'from' => Carbon::now(), 'expected' => Carbon::tomorrow()],
+ '1W' => ['frequency' => '1W', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeeks(1)],
+ 'weekly' => ['frequency' => 'weekly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeeks(1)],
+ 'week' => ['frequency' => 'week', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeeks(1)],
+ '3M' => ['frequency' => '3M', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(3)],
+ 'quarter' => ['frequency' => 'quarter', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(3)],
+ 'quarterly' => ['frequency' => 'quarterly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(3)],
+ '6M' => ['frequency' => '6M', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(6)],
+ 'half-year' => ['frequency' => 'half-year', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(6)],
+ 'year' => ['frequency' => 'year', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(1)],
+ 'yearly' => ['frequency' => 'yearly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(1)],
+ '1Y' => ['frequency' => '1Y', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(1)],
+ 'last7' => ['frequency' => 'last7', 'from' => Carbon::now(), 'expected' => Carbon::now()->addDays(7)],
+ 'last30' => ['frequency' => 'last30', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(1)],
+ 'last90' => ['frequency' => 'last90', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(3)],
+ 'last365' => ['frequency' => 'last365', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(1)],
+ 'MTD' => ['frequency' => 'MTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(1)],
+ 'QTD' => ['frequency' => 'QTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(3)],
+ 'YTD' => ['frequency' => 'YTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(1)],
+ ];
+ }
+
+ /**
+ * @dataProvider providePeriods
+ */
+ public function testGivenAFrequencyWhenCalculateTheDateThenReturnsTheExpectedDateSuccessful(string $frequency, Carbon $from, Carbon $expected)
+ {
+ $period = $this->navigation->addPeriod($from, $frequency, 0);
+ $this->assertEquals($expected->toDateString(), $period->toDateString());
+ }
+
+ public static function provideMonthPeriods(): array
+ {
+ return [
+ '1M' => ['frequency' => '1M', 'from' => Carbon::parse('2023-06-25'), 'expected' => Carbon::parse('2023-06-25')->addMonths(1)],
+ 'month' => ['frequency' => 'month', 'from' => Carbon::parse('2023-06-25'), 'expected' => Carbon::parse('2023-06-25')->addMonths(1)],
+ 'monthly' => ['frequency' => 'monthly', 'from' => Carbon::parse('2023-06-25'), 'expected' => Carbon::parse('2023-06-25')->addMonths(1)],
+ '2019-01-29 to 2019-02-28' => ['frequency' => 'monthly', 'from' => Carbon::parse('2019-01-29'), 'expected' => Carbon::parse('2019-02-28')],
+ '2019-01-30 to 2019-02-28' => ['frequency' => 'monthly', 'from' => Carbon::parse('2019-01-30'), 'expected' => Carbon::parse('2019-02-28')],
+ '2019-01-31 to 2019-02-28' => ['frequency' => 'monthly', 'from' => Carbon::parse('2019-01-31'), 'expected' => Carbon::parse('2019-02-28')],
+ '2023-03-31 to 2023-04-30' => ['frequency' => 'monthly', 'from' => Carbon::parse('2023-03-31'), 'expected' => Carbon::parse('2023-04-30')],
+ '2023-05-31 to 2023-06-30' => ['frequency' => 'monthly', 'from' => Carbon::parse('2023-05-31'), 'expected' => Carbon::parse('2023-06-30')],
+ '2023-08-31 to 2023-09-30' => ['frequency' => 'monthly', 'from' => Carbon::parse('2023-08-31'), 'expected' => Carbon::parse('2023-09-30')],
+ '2023-10-31 to 2023-11-30' => ['frequency' => 'monthly', 'from' => Carbon::parse('2023-10-31'), 'expected' => Carbon::parse('2023-11-30')],
+ ];
+ }
+
+ /**
+ * @dataProvider provideMonthPeriods
+ */
+ public function testGivenAMonthFrequencyWhenCalculateTheDateThenReturnsTheLastDayOfMonthSuccessful(string $frequency, Carbon $from, Carbon $expected)
+ {
+ $period = $this->navigation->addPeriod($from, $frequency, 0);
+ $this->assertEquals($expected->toDateString(), $period->toDateString());
+ }
+
+ public static function providePeriodsWithSkippingParam(): \Generator
+ {
+ $intervals = [
+ '2019-01-31 to 2019-02-11' => ['skip' => 10, 'frequency' => 'daily', 'from' => Carbon::parse('2019-01-31'), 'expected' => Carbon::parse('2019-02-11')],
+ '1D' => ['skip' => 1, 'frequency' => '1D', 'from' => Carbon::now(), 'expected' => Carbon::now()->addDays(2)],
+ 'daily' => ['skip' => 1, 'frequency' => 'daily', 'from' => Carbon::now(), 'expected' => Carbon::now()->addDays(2)],
+ '1W' => ['skip' => 1, 'frequency' => '1W', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeeks(2)],
+ 'weekly' => ['skip' => 1, 'frequency' => 'weekly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeeks(2)],
+ 'week' => ['skip' => 1, 'frequency' => 'week', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeeks(2)],
+ '1M' => ['skip' => 1, 'frequency' => '1M', 'from' => Carbon::parse('2023-06-25'), 'expected' => Carbon::parse('2023-06-25')->addMonths(2)],
+ 'month' => ['skip' => 1, 'frequency' => 'month', 'from' => Carbon::parse('2023-06-25'), 'expected' => Carbon::parse('2023-06-25')->addMonths(2)],
+ 'monthly' => ['skip' => 1, 'frequency' => 'monthly', 'from' => Carbon::parse('2023-06-25'), 'expected' => Carbon::parse('2023-06-25')->addMonths(2)],
+ '2019-01-29 to 2019-03-29' => ['skip' => 1, 'frequency' => 'monthly', 'from' => Carbon::parse('2019-01-29'), 'expected' => Carbon::parse('2019-03-29')],
+ '2019-01-30 to 2019-03-30' => ['skip' => 1, 'frequency' => 'monthly', 'from' => Carbon::parse('2019-01-30'), 'expected' => Carbon::parse('2019-03-30')],
+ '2019-01-31 to 2019-03-31' => ['skip' => 1, 'frequency' => 'monthly', 'from' => Carbon::parse('2019-01-31'), 'expected' => Carbon::parse('2019-03-31')],
+ '2023-03-31 to 2023-05-31' => ['skip' => 1, 'frequency' => 'monthly', 'from' => Carbon::parse('2023-03-31'), 'expected' => Carbon::parse('2023-05-31')],
+ '2023-05-31 to 2023-07-31' => ['skip' => 1, 'frequency' => 'monthly', 'from' => Carbon::parse('2023-05-31'), 'expected' => Carbon::parse('2023-07-31')],
+ '2023-08-31 to 2023-10-31' => ['skip' => 1, 'frequency' => 'monthly', 'from' => Carbon::parse('2023-08-31'), 'expected' => Carbon::parse('2023-10-31')],
+ '2023-10-31 to 2023-12-31' => ['skip' => 1, 'frequency' => 'monthly', 'from' => Carbon::parse('2023-10-31'), 'expected' => Carbon::parse('2023-12-31')],
+ '2023-01-31 to 2023-03-30' => ['skip' => 2, 'frequency' => 'monthly', 'from' => Carbon::parse('2023-01-31'), 'expected' => Carbon::parse('2023-04-30')],
+ '3M' => ['skip' => 1, 'frequency' => '3M', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(6)],
+ 'quarter' => ['skip' => 1, 'frequency' => 'quarter', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(6)],
+ 'quarterly' => ['skip' => 1, 'frequency' => 'quarterly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(6)],
+ '6M' => ['skip' => 1, 'frequency' => '6M', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(12)],
+ 'half-year' => ['skip' => 1, 'frequency' => 'half-year', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(12)],
+ 'year' => ['skip' => 1, 'frequency' => 'year', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(2)],
+ 'yearly' => ['skip' => 1, 'frequency' => 'yearly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(2)],
+ '1Y' => ['skip' => 1, 'frequency' => '1Y', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(2)],
+ '2023-02-01 to 2023-02-15' => ['skip' => 1, 'frequency' => 'last7', 'from' => Carbon::parse('2023-02-01'), 'expected' => Carbon::parse('2023-02-15')],
+ 'last7' => ['skip' => 1, 'frequency' => 'last7', 'from' => Carbon::now(), 'expected' => Carbon::now()->addDays(14)],
+ 'last30' => ['skip' => 1, 'frequency' => 'last30', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(2)],
+ 'last90' => ['skip' => 1, 'frequency' => 'last90', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(6)],
+ 'last365' => ['skip' => 1, 'frequency' => 'last365', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(2)],
+ 'MTD' => ['skip' => 1, 'frequency' => 'MTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(2)],
+ 'QTD' => ['skip' => 1, 'frequency' => 'QTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(6)],
+ 'YTD' => ['skip' => 1, 'frequency' => 'YTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(2)],
+ ];
+ foreach ($intervals as $interval) {
+ yield "{$interval["frequency"]} {$interval["from"]->toDateString()} to {$interval["expected"]->toDateString()}" => $interval;
+ }
+ }
+
+ /**
+ * @dataProvider providePeriodsWithSkippingParam
+ */
+ public function testGivenAFrequencyAndSkipIntervalWhenCalculateTheDateThenReturnsTheSkippedDateSuccessful(int $skip, string $frequency, Carbon $from, Carbon $expected)
+ {
+ $period = $this->navigation->addPeriod($from, $frequency, $skip);
+ $this->assertEquals($expected->toDateString(), $period->toDateString());
+ }
+
+ public static function provideFrequencies(): array
+ {
+ return [
+ Periodicity::Daily->name => ['periodicity' => Periodicity::Daily, 'from' => Carbon::now(), 'expected' => Carbon::tomorrow()],
+ Periodicity::Weekly->name => ['periodicity' => Periodicity::Weekly, 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeeks(1)],
+ Periodicity::Fortnightly->name => ['periodicity' => Periodicity::Fortnightly, 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeeks(2)],
+ Periodicity::Monthly->name => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(1)],
+ '2019-01-01 to 2019-02-01' => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::parse('2019-01-01'), 'expected' => Carbon::parse('2019-02-01')],
+ '2019-01-29 to 2019-02-28' => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::parse('2019-01-29'), 'expected' => Carbon::parse('2019-02-28')],
+ '2019-01-30 to 2019-02-28' => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::parse('2019-01-30'), 'expected' => Carbon::parse('2019-02-28')],
+ '2019-01-31 to 2019-02-28' => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::parse('2019-01-31'), 'expected' => Carbon::parse('2019-02-28')],
+ '2023-03-31 to 2023-04-30' => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::parse('2023-03-31'), 'expected' => Carbon::parse('2023-04-30')],
+ '2023-05-31 to 2023-06-30' => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::parse('2023-05-31'), 'expected' => Carbon::parse('2023-06-30')],
+ '2023-08-31 to 2023-09-30' => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::parse('2023-08-31'), 'expected' => Carbon::parse('2023-09-30')],
+ '2023-10-31 to 2023-11-30' => ['periodicity' => Periodicity::Monthly, 'from' => Carbon::parse('2023-10-31'), 'expected' => Carbon::parse('2023-11-30')],
+ Periodicity::Quarterly->name => ['periodicity' => Periodicity::Quarterly, 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(3)],
+ '2019-01-29 to 2020-04-29' => ['periodicity' => Periodicity::Quarterly, 'from' => Carbon::parse('2019-01-29'), 'expected' => Carbon::parse('2019-04-29')],
+ '2019-01-30 to 2020-04-30' => ['periodicity' => Periodicity::Quarterly, 'from' => Carbon::parse('2019-01-30'), 'expected' => Carbon::parse('2019-04-30')],
+ '2019-01-31 to 2020-04-30' => ['periodicity' => Periodicity::Quarterly, 'from' => Carbon::parse('2019-01-31'), 'expected' => Carbon::parse('2019-04-30')],
+ Periodicity::HalfYearly->name => ['periodicity' => Periodicity::HalfYearly, 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonths(6)],
+ '2019-01-31 to 2020-07-29' => ['periodicity' => Periodicity::HalfYearly, 'from' => Carbon::parse('2019-01-29'), 'expected' => Carbon::parse('2019-07-29')],
+ '2019-01-31 to 2020-07-30' => ['periodicity' => Periodicity::HalfYearly, 'from' => Carbon::parse('2019-01-30'), 'expected' => Carbon::parse('2019-07-30')],
+ '2019-01-31 to 2020-07-31' => ['periodicity' => Periodicity::HalfYearly, 'from' => Carbon::parse('2019-01-31'), 'expected' => Carbon::parse('2019-07-31')],
+ Periodicity::Yearly->name => ['periodicity' => Periodicity::Yearly, 'from' => Carbon::now(), 'expected' => Carbon::now()->addYears(1)],
+ '2020-02-29 to 2021-02-28' => ['periodicity' => Periodicity::Yearly, 'from' => Carbon::parse('2020-02-29'), 'expected' => Carbon::parse('2021-02-28')],
+ ];
+ }
+
+ /**
+ * @dataProvider provideFrequencies
+ */
+ public function testGivenAIntervalWhenCallTheNextDateByIntervalMethodThenReturnsTheExpectedDateSuccessful(Periodicity $periodicity, Carbon $from, Carbon $expected)
+ {
+ $period = $this->navigation->nextDateByInterval($from, $periodicity);
+ $this->assertEquals($expected->toDateString(), $period->toDateString());
+ }
+}