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