diff --git a/.gitattributes b/.gitattributes index 3e9ca7c36..3d046086b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,12 +8,10 @@ Dockerfile export-ignore /benchmark/ export-ignore /hack/ export-ignore /phpbench.json export-ignore -/phpspec.yml.dist export-ignore /phpstan-baseline.neon export-ignore /phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore /psalm.xml export-ignore -/spec/ export-ignore /tests/ export-ignore /doc/ export-ignore diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f9d6b7bb4..1c721fd96 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install uses: docker://composer diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c5900f43..2a64fe92b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up locales run: ./hack/setup-locales.sh @@ -40,6 +40,7 @@ jobs: - '8.0' - '8.1' - '8.2' + - '8.3' steps: - name: Set up PHP @@ -53,7 +54,7 @@ jobs: run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up locales run: ./hack/setup-locales.sh @@ -70,7 +71,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 @@ -95,7 +96,7 @@ jobs: architecture: 'x64' - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install dependencies run: | diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index df23ac244..4e3bfafa0 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 diff --git a/.gitignore b/.gitignore index 42674bbe6..7d4483493 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ .php_cs.cache .phpunit.result.cache /build/ -/phpspec.yml /phpunit.xml /vendor/ diff --git a/README.md b/README.md index 4e2ebb5cc..c782769ae 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Please see the [official documentation](http://moneyphp.org). ## Testing -We try to follow BDD and TDD, as such we use both [phpspec](http://www.phpspec.net) and [phpunit](https://phpunit.de) to test this library. +We try to follow TDD by using [phpunit](https://phpunit.de) to test this library. ```bash $ composer test diff --git a/composer.json b/composer.json index da73d3110..9a8f10c46 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ ], "homepage": "http://moneyphp.org", "require": { - "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", "ext-bcmath": "*", "ext-filter": "*", "ext-json": "*" @@ -42,11 +42,10 @@ "php-http/message": "^1.11.0", "php-http/mock-client": "^1.4.1", "phpbench/phpbench": "^1.2.5", - "phpspec/phpspec": "^7.3", "phpunit/phpunit": "^9.5.4", "psalm/plugin-phpunit": "^0.18.4", "psr/cache": "^1.0.1", - "vimeo/psalm": "~5.3.0" + "vimeo/psalm": "~5.15.0" }, "suggest": { "ext-gmp": "Calculate without integer limits", @@ -65,8 +64,7 @@ "autoload-dev": { "psr-4": { "Benchmark\\Money\\": "benchmark/", - "Tests\\Money\\": "tests/", - "spec\\Money\\": "spec/" + "Tests\\Money\\": "tests/" } }, "config": { @@ -90,7 +88,6 @@ ], "clean": "rm -rf build/ vendor/", "test": [ - "vendor/bin/phpspec run", "vendor/bin/phpunit -v", "vendor/bin/phpbench run", "vendor/bin/psalm", diff --git a/doc/conf.py b/doc/conf.py index 13480774d..1bd0489e2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -78,7 +78,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/doc/features/teller.rst b/doc/features/teller.rst index b94335c87..36514b122 100644 --- a/doc/features/teller.rst +++ b/doc/features/teller.rst @@ -24,14 +24,14 @@ The Teller offers these methods: * operation - * ``absolute($amount) : string`` Returns an absoute monetary amount. + * ``absolute($amount) : string`` Returns an absolute monetary amount. * ``add($amount, $other, ...$others) : string`` Adds one or more monetary amounts to a monetary amount. * ``divide($amount, $divisor) : string`` Divides a monetary amount by a divisor. - * ``mod($amount, $divisor)`` Retuns the mod of one amount by another. + * ``mod($amount, $divisor)`` Returns the mod of one amount by another. * ``multiply($amount, $multiplier) : string`` Multiplies a monetary amount by a multiplier. * ``negative($amount) : string`` Negates a monetary amount. * ``ratioOf($amount, $other)`` Determines the ratio of one monetary amount to another. - * ``subtract($amount, $other, ...$others) : string`` Subracts one or more monetary amounts from a monetary amount. + * ``subtract($amount, $other, ...$others) : string`` Subtracts one or more monetary amounts from a monetary amount. * comparison diff --git a/doc/requirements.txt b/doc/requirements.txt index cadd68832..868f60ea1 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,6 +1,6 @@ git+https://github.com/fabpot/sphinx-php.git -sphinx~=4.3.1 -sphinx-rtd-theme==1.0.0 +sphinx~=7.2.6 +sphinx-rtd-theme~=1.3.0 git+https://github.com/markstory/sphinxcontrib-phpdomain.git -sphinxcontrib-spelling==7.2.1 +sphinxcontrib-spelling==8.0.0 pyenchant diff --git a/doc/spelling_word_list.txt b/doc/spelling_word_list.txt index 788c4f356..26392097e 100644 --- a/doc/spelling_word_list.txt +++ b/doc/spelling_word_list.txt @@ -1,5 +1,7 @@ amongst integrations +codebases +formatter formatters eg Bitcoin diff --git a/phpspec.yml.dist b/phpspec.yml.dist deleted file mode 100644 index 069500c38..000000000 --- a/phpspec.yml.dist +++ /dev/null @@ -1,5 +0,0 @@ -suites: - money_suite: - namespace: Money - psr4_prefix: Money -formatter.name: pretty diff --git a/spec/Calculator/BcMathCalculatorSpec.php b/spec/Calculator/BcMathCalculatorSpec.php deleted file mode 100644 index adb16ddcd..000000000 --- a/spec/Calculator/BcMathCalculatorSpec.php +++ /dev/null @@ -1,18 +0,0 @@ -shouldHaveType(BcMathCalculator::class); - } -} diff --git a/spec/Calculator/CalculatorBehavior.php b/spec/Calculator/CalculatorBehavior.php deleted file mode 100644 index e19df648b..000000000 --- a/spec/Calculator/CalculatorBehavior.php +++ /dev/null @@ -1,92 +0,0 @@ -shouldImplement(Calculator::class); - } - - public function it_compares_two_values(): void - { - $this->compare(2, 1)->shouldReturn(1); - $this->compare(1, 2)->shouldReturn(-1); - $this->compare(1, 1)->shouldReturn(0); - } - - public function it_adds_two_values(): void - { - $this->add(rand(-100, 100), rand(-100, 100))->shouldBeString(); - } - - public function it_subtracts_a_value_from_another(): void - { - $this->subtract(rand(-100, 100), rand(-100, 100))->shouldBeString(); - } - - public function it_multiplies_a_value_by_another(): void - { - $this->multiply(rand(-100, 100), rand(-100, 100))->shouldBeString(); - } - - public function it_divides_a_value_by_another(): void - { - $this->divide(rand(-100, 100), rand(1, 100))->shouldBeString(); - } - - public function it_ceils_a_value(): void - { - $this->ceil(rand(-100, 100) / 100)->shouldBeString(); - } - - public function it_floors_a_value(): void - { - $this->floor(rand(-100, 100) / 100)->shouldBeString(); - } - - public function it_calculates_the_absolute_value(): void - { - $result = $this->absolute(rand(1, 100)); - - $result->shouldBeGreaterThanZero(); - $result->shouldBeString(); - - $result = $this->absolute(rand(-100, -1)); - - $result->shouldBeGreaterThanZero(); - $result->shouldBeString(); - } - - public function it_shares_a_value(): void - { - $this->share('10', '2', '4')->shouldBeString(); - } - - public function it_calculates_the_modulus(): void - { - $this->mod('11', '5')->shouldBeString(); - } - - /** {@inheritDoc} */ - public function getMatchers(): array - { - return [ - 'beGreaterThanZero' => static function ($subject) { - return $subject > 0; - }, - ]; - } -} diff --git a/spec/Calculator/GmpCalculatorSpec.php b/spec/Calculator/GmpCalculatorSpec.php deleted file mode 100644 index c17a846fd..000000000 --- a/spec/Calculator/GmpCalculatorSpec.php +++ /dev/null @@ -1,18 +0,0 @@ -shouldHaveType(GmpCalculator::class); - } -} diff --git a/spec/ConverterSpec.php b/spec/ConverterSpec.php deleted file mode 100644 index d674f26cd..000000000 --- a/spec/ConverterSpec.php +++ /dev/null @@ -1,74 +0,0 @@ -beConstructedWith($currencies, $exchange); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(Converter::class); - } - - public function it_converts_to_a_different_currency(Currencies $currencies, Exchange $exchange): void - { - $baseCurrency = new Currency($baseCurrencyCode = 'ABC'); - $counterCurrency = new Currency($counterCurrencyCode = 'XYZ'); - $pair = new CurrencyPair($baseCurrency, $counterCurrency, '0.5'); - - $currencies->subunitFor($baseCurrency)->willReturn(100); - $currencies->subunitFor($counterCurrency)->willReturn(100); - - $exchange->quote($baseCurrency, $counterCurrency)->willReturn($pair); - - $money = $this->convert( - new Money(2, new Currency($baseCurrencyCode)), - $counterCurrency - ); - - $money->shouldHaveType(Money::class); - $money->getAmount()->shouldBe('1'); - $money->getCurrency()->getCode()->shouldBe($counterCurrencyCode); - } - - public function it_converts_using_rounding_modes(Currencies $currencies, Exchange $exchange): void - { - $baseCurrency = new Currency('EUR'); - $counterCurrency = new Currency('USD'); - $pair = new CurrencyPair($baseCurrency, $counterCurrency, '1.25'); - - $currencies->subunitFor($baseCurrency)->willReturn(2); - $currencies->subunitFor($counterCurrency)->willReturn(2); - $exchange->quote($baseCurrency, $counterCurrency)->willReturn($pair); - - $money = new Money(10, $baseCurrency); - - $resultMoney = $this->convert($money, $counterCurrency); - - $resultMoney->shouldHaveType(Money::class); - $resultMoney->getAmount()->shouldBeLike(13); - $resultMoney->getCurrency()->getCode()->shouldReturn('USD'); - - $resultMoney = $this->convert($money, $counterCurrency, PHP_ROUND_HALF_DOWN); - - $resultMoney->shouldHaveType(Money::class); - $resultMoney->getAmount()->shouldBeLike(12); - $resultMoney->getCurrency()->getCode()->shouldReturn('USD'); - } -} diff --git a/spec/Currencies/BitcoinCurrenciesSpec.php b/spec/Currencies/BitcoinCurrenciesSpec.php deleted file mode 100644 index 85aed918f..000000000 --- a/spec/Currencies/BitcoinCurrenciesSpec.php +++ /dev/null @@ -1,36 +0,0 @@ -shouldHaveType(BitcoinCurrencies::class); - } - - public function it_is_a_currency_repository(): void - { - $this->shouldImplement(Currencies::class); - } - - public function it_contains_bitcoin(): void - { - $this->contains(new Currency('XBT'))->shouldReturn(true); - $this->contains(new Currency('EUR'))->shouldReturn(false); - } - - public function it_is_iterable(): void - { - $this->getIterator()->shouldHaveCurrency('XBT'); - } -} diff --git a/spec/Currencies/CurrencyListSpec.php b/spec/Currencies/CurrencyListSpec.php deleted file mode 100644 index 1bc814acc..000000000 --- a/spec/Currencies/CurrencyListSpec.php +++ /dev/null @@ -1,49 +0,0 @@ -beConstructedWith([ - 'MY1' => 2, - 'MY2' => 0, - 'MY3' => 1, - ]); - } - - public function it_is_a_currency_repository(): void - { - $this->shouldImplement(Currencies::class); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(CurrencyList::class); - } - - public function it_contains_custom_currency(): void - { - $this->contains(new Currency('MY1'))->shouldReturn(true); - } - - public function it_does_not_contain_currency(): void - { - $this->contains(new Currency('EUR'))->shouldReturn(false); - } - - public function it_is_iterable(): void - { - $this->getIterator()->shouldHaveCurrency('MY1'); - } -} diff --git a/spec/Currencies/ISOCurrenciesSpec.php b/spec/Currencies/ISOCurrenciesSpec.php deleted file mode 100644 index 331af8634..000000000 --- a/spec/Currencies/ISOCurrenciesSpec.php +++ /dev/null @@ -1,24 +0,0 @@ -shouldHaveType(ISOCurrencies::class); - } - - public function it_is_a_currency_repository(): void - { - $this->shouldImplement(Currencies::class); - } -} diff --git a/spec/Currencies/Matchers.php b/spec/Currencies/Matchers.php deleted file mode 100644 index 6acb9c845..000000000 --- a/spec/Currencies/Matchers.php +++ /dev/null @@ -1,32 +0,0 @@ - */ - public function getMatchers(): array - { - return [ - 'haveCurrency' => static function (mixed $subject, mixed $value): bool { - assert(is_iterable($subject)); - - foreach ($subject as $currency) { - assert($currency instanceof Currency); - if ($currency->getCode() === $value) { - return true; - } - } - - return false; - }, - ]; - } -} diff --git a/spec/CurrencyPairSpec.php b/spec/CurrencyPairSpec.php deleted file mode 100644 index ae33466f3..000000000 --- a/spec/CurrencyPairSpec.php +++ /dev/null @@ -1,51 +0,0 @@ -beConstructedWith(new Currency('EUR'), new Currency('USD'), '1.250000'); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(CurrencyPair::class); - } - - public function it_is_json_serializable(): void - { - $this->shouldImplement(JsonSerializable::class); - } - - public function it_has_currencies_and_ratio(): void - { - $this->beConstructedWith($base = new Currency('EUR'), $counter = new Currency('USD'), $ratio = '1.0'); - - $this->getBaseCurrency()->shouldReturn($base); - $this->getCounterCurrency()->shouldReturn($counter); - $this->getConversionRatio()->shouldReturn($ratio); - } - - public function it_equals_to_another_currency_pair(): void - { - $this->equals(new CurrencyPair(new Currency('GBP'), new Currency('USD'), '1.250000'))->shouldReturn(false); - $this->equals(new CurrencyPair(new Currency('EUR'), new Currency('GBP'), '1.250000'))->shouldReturn(false); - $this->equals(new CurrencyPair(new Currency('EUR'), new Currency('USD'), '1.5000'))->shouldReturn(false); - $this->equals(new CurrencyPair(new Currency('EUR'), new Currency('USD'), '1.250000'))->shouldReturn(true); - } - - public function it_throws_an_exception_when_iso_string_cannot_be_parsed(): void - { - $this->shouldThrow(InvalidArgumentException::class)->duringCreateFromIso('1.250000'); - } -} diff --git a/spec/CurrencySpec.php b/spec/CurrencySpec.php deleted file mode 100644 index b492b26fb..000000000 --- a/spec/CurrencySpec.php +++ /dev/null @@ -1,38 +0,0 @@ -beConstructedWith('EUR'); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(Currency::class); - } - - public function it_is_json_serializable(): void - { - $this->shouldImplement(JsonSerializable::class); - } - - public function it_has_a_code(): void - { - $this->getCode()->shouldReturn('EUR'); - } - - public function it_equals_to_a_currency_with_the_same_code(): void - { - $this->equals(new Currency('EUR'))->shouldReturn(true); - $this->equals(new Currency('USD'))->shouldReturn(false); - } -} diff --git a/spec/Exception/FormatterExceptionSpec.php b/spec/Exception/FormatterExceptionSpec.php deleted file mode 100644 index b105b3394..000000000 --- a/spec/Exception/FormatterExceptionSpec.php +++ /dev/null @@ -1,28 +0,0 @@ -shouldHaveType(FormatterException::class); - } - - public function it_is_an_exception(): void - { - $this->shouldHaveType(Exception::class); - } - - public function it_is_a_runtime_exception(): void - { - $this->shouldHaveType(RuntimeException::class); - } -} diff --git a/spec/Exception/ParserExceptionSpec.php b/spec/Exception/ParserExceptionSpec.php deleted file mode 100644 index b4d7aab5d..000000000 --- a/spec/Exception/ParserExceptionSpec.php +++ /dev/null @@ -1,28 +0,0 @@ -shouldHaveType(ParserException::class); - } - - public function it_is_an_exception(): void - { - $this->shouldHaveType(Exception::class); - } - - public function it_is_a_runtime_exception(): void - { - $this->shouldHaveType(RuntimeException::class); - } -} diff --git a/spec/Exception/UnknownCurrencyExceptionSpec.php b/spec/Exception/UnknownCurrencyExceptionSpec.php deleted file mode 100644 index 9170d40e3..000000000 --- a/spec/Exception/UnknownCurrencyExceptionSpec.php +++ /dev/null @@ -1,28 +0,0 @@ -shouldHaveType(UnknownCurrencyException::class); - } - - public function it_is_an_exception(): void - { - $this->shouldHaveType(Exception::class); - } - - public function it_is_a_domain_exception(): void - { - $this->shouldHaveType(DomainException::class); - } -} diff --git a/spec/Exception/UnresolvableCurrencyPairExceptionSpec.php b/spec/Exception/UnresolvableCurrencyPairExceptionSpec.php deleted file mode 100644 index a559bee47..000000000 --- a/spec/Exception/UnresolvableCurrencyPairExceptionSpec.php +++ /dev/null @@ -1,35 +0,0 @@ -shouldHaveType(UnresolvableCurrencyPairException::class); - } - - public function it_is_an_exception(): void - { - $this->shouldHaveType(Exception::class); - } - - public function it_is_an_invalid_argument_exception(): void - { - $this->shouldHaveType(InvalidArgumentException::class); - } - - public function it_accepts_a_currency_pair(): void - { - $this->createFromCurrencies(new Currency('EUR'), new Currency('USD')) - ->shouldHaveType(UnresolvableCurrencyPairException::class); - } -} diff --git a/spec/Exchange/FixedExchangeSpec.php b/spec/Exchange/FixedExchangeSpec.php deleted file mode 100644 index d4c82ac22..000000000 --- a/spec/Exchange/FixedExchangeSpec.php +++ /dev/null @@ -1,51 +0,0 @@ -beConstructedWith([ - 'EUR' => ['USD' => '1.25'], - ]); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(FixedExchange::class); - } - - public function it_is_an_exchange(): void - { - $this->shouldImplement(Exchange::class); - } - - public function it_exchanges_currencies(): void - { - $baseCurrency = new Currency('EUR'); - $counterCurrency = new Currency('USD'); - - $currencyPair = $this->quote($baseCurrency, $counterCurrency); - - $currencyPair->shouldHaveType(CurrencyPair::class); - $currencyPair->getBaseCurrency()->shouldReturn($baseCurrency); - $currencyPair->getCounterCurrency()->shouldReturn($counterCurrency); - $currencyPair->getConversionRatio()->shouldReturn('1.25'); - } - - public function it_cannot_exchange_currencies(): void - { - $this->shouldThrow(UnresolvableCurrencyPairException::class) - ->duringQuote(new Currency('USD'), new Currency('EUR')); - } -} diff --git a/spec/Formatter/BitcoinMoneyFormatterSpec.php b/spec/Formatter/BitcoinMoneyFormatterSpec.php deleted file mode 100644 index 463373879..000000000 --- a/spec/Formatter/BitcoinMoneyFormatterSpec.php +++ /dev/null @@ -1,53 +0,0 @@ -beConstructedWith(2, $bitcoinCurrencies); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(BitcoinMoneyFormatter::class); - } - - public function it_is_a_money_formatter(): void - { - $this->shouldImplement(MoneyFormatter::class); - } - - public function it_formats_money(Currencies $bitcoinCurrencies): void - { - $this->beConstructedWith(1, $bitcoinCurrencies); - - $currency = new Currency('XBT'); - $money = new Money(1000000, $currency); - - $bitcoinCurrencies->subunitFor($currency)->willReturn(8); - - $formatted = $this->format($money); - - $formatted->shouldBeString(); - $formatted->shouldContain(Currencies\BitcoinCurrencies::SYMBOL); - } - - public function it_throws_an_exception_when_currency_is_not_bitcoin(): void - { - $money = new Money(5, new Currency('USD')); - - $this->shouldThrow(FormatterException::class)->duringFormat($money); - } -} diff --git a/spec/Formatter/DecimalMoneyFormatterSpec.php b/spec/Formatter/DecimalMoneyFormatterSpec.php deleted file mode 100644 index c42fe23bd..000000000 --- a/spec/Formatter/DecimalMoneyFormatterSpec.php +++ /dev/null @@ -1,39 +0,0 @@ -beConstructedWith($currencies); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(DecimalMoneyFormatter::class); - } - - public function it_is_a_money_formatter(): void - { - $this->shouldImplement(MoneyFormatter::class); - } - - public function it_formats_money(Currencies $currencies): void - { - $money = new Money(100, new Currency('EUR')); - - $currencies->subunitFor($money->getCurrency())->willReturn(2); - - $this->format($money)->shouldReturn('1.00'); - } -} diff --git a/spec/Formatter/IntlMoneyFormatterSpec.php b/spec/Formatter/IntlMoneyFormatterSpec.php deleted file mode 100644 index 202b96bbc..000000000 --- a/spec/Formatter/IntlMoneyFormatterSpec.php +++ /dev/null @@ -1,41 +0,0 @@ -beConstructedWith($numberFormatter, $currencies); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(IntlMoneyFormatter::class); - } - - public function it_is_a_money_formatter(): void - { - $this->shouldImplement(MoneyFormatter::class); - } - - public function it_formats_money(NumberFormatter $numberFormatter, Currencies $currencies): void - { - $money = new Money(1, new Currency('EUR')); - - $numberFormatter->formatCurrency('0.01', 'EUR')->willReturn('€1.00'); - $currencies->subunitFor($money->getCurrency())->willReturn(2); - - $this->format($money)->shouldReturn('€1.00'); - } -} diff --git a/spec/MoneySpec.php b/spec/MoneySpec.php deleted file mode 100644 index 9adf3da27..000000000 --- a/spec/MoneySpec.php +++ /dev/null @@ -1,144 +0,0 @@ -beConstructedWith(self::AMOUNT, new Currency(self::CURRENCY)); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(Money::class); - } - - public function it_is_json_serializable(): void - { - $this->shouldImplement(JsonSerializable::class); - } - - public function it_has_an_amount(): void - { - $this->getAmount()->shouldBeLike(self::AMOUNT); - } - - public function it_has_a_currency(): void - { - $currency = $this->getCurrency(); - - $currency->shouldHaveType(Currency::class); - $currency->equals(new Currency(self::CURRENCY))->shouldReturn(true); - } - - public function it_throws_an_exception_when_amount_is_not_numeric(): void - { - $this->beConstructedWith('ONE', new Currency(self::CURRENCY)); - - $this->shouldThrow(InvalidArgumentException::class)->duringInstantiation(); - } - - public function it_constructs_integer(): void - { - $this->beConstructedWith(5, new Currency(self::CURRENCY)); - } - - public function it_constructs_string(): void - { - $this->beConstructedWith('5', new Currency(self::CURRENCY)); - } - - public function it_constructs_integer_with_decimals_of_zero(): void - { - $this->beConstructedWith('5.00', new Currency(self::CURRENCY)); - } - - public function it_constructs_integer_with_plus(): void - { - $this->beConstructedWith('+500', new Currency(self::CURRENCY)); - - $this->shouldNotThrow(InvalidArgumentException::class)->duringInstantiation(); - } - - public function it_tests_currency_equality(): void - { - $this->isSameCurrency(new Money(self::AMOUNT, new Currency(self::CURRENCY)))->shouldReturn(true); - $this->isSameCurrency(new Money(self::AMOUNT, new Currency(self::OTHER_CURRENCY)))->shouldReturn(false); - } - - public function it_tests_currency_equality_with_multiple_arguments(): void - { - $this->isSameCurrency( - new Money(self::AMOUNT, new Currency(self::CURRENCY)), - new Money(self::AMOUNT, new Currency(self::CURRENCY)) - )->shouldReturn(true); - - $this->isSameCurrency( - new Money(self::AMOUNT, new Currency(self::CURRENCY)), - new Money(self::AMOUNT, new Currency(self::OTHER_CURRENCY)) - )->shouldReturn(false); - } - - public function it_equals_to_another_money(): void - { - $this->equals(new Money(self::AMOUNT, new Currency(self::CURRENCY)))->shouldReturn(true); - } - - public function it_returns_the_same_money_when_no_addends_are_provided(): void - { - $money = $this->add(); - - $money->getAmount()->shouldBe($this->getAmount()); - } - - public function it_returns_the_same_money_when_no_subtrahends_are_provided(): void - { - $money = $this->subtract(); - - $money->getAmount()->shouldBe($this->getAmount()); - } - - public function it_throws_an_exception_when_allocation_target_is_empty(): void - { - $this->shouldThrow(InvalidArgumentException::class)->duringAllocate([]); - } - - /** {@inheritDoc} */ - public function getMatchers(): array - { - return [ - 'equalAllocation' => function ($subject, $value) { - foreach ($subject as $key => $money) { - assert($money instanceof Money); - $compareTo = new Money($value[$key], $money->getCurrency()); - if ($money->equals($compareTo) === false) { - return false; - } - } - - return true; - }, - ]; - } -} diff --git a/spec/NumberSpec.php b/spec/NumberSpec.php deleted file mode 100644 index d2a90263e..000000000 --- a/spec/NumberSpec.php +++ /dev/null @@ -1,38 +0,0 @@ -beConstructedWith('1'); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(Number::class); - } - - public function it_throws_an_exception_when_number_is_invalid(): void - { - $this->beConstructedWith('ONE'); - - $this->shouldThrow(InvalidArgumentException::class)->duringInstantiation(); - } - - public function it_creates_a_number_from_float(): void - { - $number = $this->fromFloat(1.1); - - $number->shouldHaveType(Number::class); - $number->__toString()->shouldReturn('1.1'); - } -} diff --git a/spec/Parser/BitcoinMoneyParserSpec.php b/spec/Parser/BitcoinMoneyParserSpec.php deleted file mode 100644 index 9cee0e610..000000000 --- a/spec/Parser/BitcoinMoneyParserSpec.php +++ /dev/null @@ -1,48 +0,0 @@ -beConstructedWith(2); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(BitcoinMoneyParser::class); - } - - public function it_is_a_money_parser(): void - { - $this->shouldImplement(MoneyParser::class); - } - - public function it_parses_money(): void - { - $money = $this->parse('Ƀ1000.00'); - - $money->shouldHaveType(Money::class); - $money->getCurrency()->getCode()->shouldReturn(BitcoinCurrencies::CODE); - } - - public function it_does_not_parse_a_different_currency(): void - { - $this->shouldThrow(ParserException::class)->duringParse('€1.00'); - } - - public function it_does_not_parse_an_invalid_value(): void - { - $this->shouldThrow(ParserException::class)->duringParse(true); - } -} diff --git a/spec/Parser/DecimalMoneyParserSpec.php b/spec/Parser/DecimalMoneyParserSpec.php deleted file mode 100644 index 166c8adb9..000000000 --- a/spec/Parser/DecimalMoneyParserSpec.php +++ /dev/null @@ -1,58 +0,0 @@ -beConstructedWith($currencies); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(DecimalMoneyParser::class); - } - - public function it_is_a_money_parser(): void - { - $this->shouldImplement(MoneyParser::class); - } - - public function it_parses_money(Currencies $currencies): void - { - $currencies->subunitFor(Argument::type(Currency::class))->willReturn(2); - - $money = $this->parse('1.00', new Currency('EUR')); - - $money->shouldHaveType(Money::class); - $money->getAmount()->shouldReturn('100'); - $money->getCurrency()->getCode()->shouldReturn('EUR'); - } - - public function it_throws_an_exception_when_there_is_no_currency(): void - { - $this->shouldThrow(ParserException::class)->duringParse('100'); - } - - public function it_throws_an_exception_when_money_includes_currency_symbol(): void - { - $this->shouldThrow(ParserException::class)->duringParse('€ 100', new Currency('EUR')); - } - - public function it_throws_an_exception_when_money_is_not_a_valid_decimal(): void - { - $this->shouldThrow(ParserException::class)->duringParse('INVALID', new Currency('EUR')); - } -} diff --git a/spec/Parser/IntlMoneyParserSpec.php b/spec/Parser/IntlMoneyParserSpec.php deleted file mode 100644 index 1d2e60bfd..000000000 --- a/spec/Parser/IntlMoneyParserSpec.php +++ /dev/null @@ -1,60 +0,0 @@ -beConstructedWith($numberFormatter, $currencies); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(IntlMoneyParser::class); - } - - public function it_is_a_money_parser(): void - { - $this->shouldImplement(MoneyParser::class); - } - - public function it_parses_money(NumberFormatter $numberFormatter, Currencies $currencies): void - { - $currencyString = ''; - - $numberFormatter->parseCurrency('€1.00', $currencyString)->willReturn(1); - $currencies->subunitFor(Argument::type(Currency::class))->willReturn(2); - - $currency = new Currency('EUR'); - $money = $this->parse('€1.00', $currency); - - $money->shouldHaveType(Money::class); - $money->getAmount()->shouldReturn('100'); - $money->getCurrency()->getCode()->shouldReturn('EUR'); - } - - public function it_throws_an_exception_when_money_cannot_be_parsed(NumberFormatter $numberFormatter): void - { - $currencyString = ''; - - $numberFormatter->parseCurrency('INVALID', $currencyString)->willReturn(false); - $numberFormatter->getErrorMessage()->willReturn('Some message'); - - $currency = new Currency('EUR'); - - $this->shouldThrow(ParserException::class)->duringParse('INVALID', $currency); - } -} diff --git a/src/Calculator/BcMathCalculator.php b/src/Calculator/BcMathCalculator.php index 087b3c05a..123af44c6 100644 --- a/src/Calculator/BcMathCalculator.php +++ b/src/Calculator/BcMathCalculator.php @@ -226,6 +226,6 @@ public static function mod(string $amount, string $divisor): string throw InvalidArgumentException::moduloByZero(); } - return bcmod($amount, $divisor) ?? '0'; + return bcmod($amount, $divisor); } } diff --git a/src/Currencies/CryptoCurrencies.php b/src/Currencies/CryptoCurrencies.php index b1a400c81..e57911edc 100644 --- a/src/Currencies/CryptoCurrencies.php +++ b/src/Currencies/CryptoCurrencies.php @@ -81,15 +81,12 @@ private function getCurrencies(): array * symbol: non-empty-string, * minorUnit: positive-int|0 * }> - * - * @psalm-suppress MoreSpecificReturnType do not specify all keys and values */ private function loadCurrencies(): array { $file = __DIR__ . '/../../resources/binance.php'; if (is_file($file)) { - /** @psalm-suppress LessSpecificReturnStatement */ return require $file; } diff --git a/src/Currencies/ISOCurrencies.php b/src/Currencies/ISOCurrencies.php index 415a14ca0..f2890b310 100644 --- a/src/Currencies/ISOCurrencies.php +++ b/src/Currencies/ISOCurrencies.php @@ -101,15 +101,12 @@ private function getCurrencies(): array * minorUnit: positive-int|0, * numericCode: positive-int * }> - * - * @psalm-suppress MoreSpecificReturnType do not specify all keys and values */ private function loadCurrencies(): array { $file = __DIR__ . '/../../resources/currency.php'; if (is_file($file)) { - /** @psalm-suppress LessSpecificReturnStatement */ return require $file; } diff --git a/tests/Currencies/CachedCurrenciesTest.php b/tests/Currencies/CachedCurrenciesTest.php index 38ce1de71..0993e817c 100644 --- a/tests/Currencies/CachedCurrenciesTest.php +++ b/tests/Currencies/CachedCurrenciesTest.php @@ -122,4 +122,71 @@ public function it_is_iterable(): void iterator_to_array(new CachedCurrencies($wrappedCurrencies, $cache)) ); } + + /** @test */ + public function it_checks_subunits_from_the_cache(): void + { + $currency = new Currency('EUR'); + + $hit = $this->createMock(CacheItemInterface::class); + $cache = $this->createMock(CacheItemPoolInterface::class); + $wrappedCurrencies = $this->createMock(Currencies::class); + + $hit->method('isHit') + ->willReturn(true); + $hit->expects(self::never()) + ->method('set'); + $hit->method('get') + ->willReturn(2); + + $cache->method('getItem') + ->with('currency|subunit|EUR') + ->willReturn($hit); + $cache->expects(self::never()) + ->method('save'); + + $wrappedCurrencies->expects(self::never()) + ->method('subunitFor'); + + self::assertEquals( + 2, + (new CachedCurrencies($wrappedCurrencies, $cache)) + ->subunitFor($currency) + ); + } + + /** @test */ + public function it_saves_subunits_to_the_cache(): void + { + $currency = new Currency('EUR'); + + $hit = $this->createMock(CacheItemInterface::class); + $cache = $this->createMock(CacheItemPoolInterface::class); + $wrappedCurrencies = $this->createMock(Currencies::class); + + $hit->method('isHit') + ->willReturn(false); + $hit->expects(self::once()) + ->method('set') + ->with(2); + $hit->expects(self::once()) + ->method('get') + ->willReturn(2); + + $cache->method('getItem') + ->with('currency|subunit|EUR') + ->willReturn($hit); + $cache->expects(self::once()) + ->method('save'); + + $wrappedCurrencies->expects(self::once()) + ->method('subunitFor') + ->willReturn(2); + + self::assertEquals( + 2, + (new CachedCurrencies($wrappedCurrencies, $cache)) + ->subunitFor($currency) + ); + } } diff --git a/tests/CurrencyPairTest.php b/tests/CurrencyPairTest.php index bba68ce93..7ccdfebe5 100644 --- a/tests/CurrencyPairTest.php +++ b/tests/CurrencyPairTest.php @@ -16,54 +16,64 @@ final class CurrencyPairTest extends TestCase /** * @test */ - public function itConvertsToJson(): void - { - $expectedJson = '{"baseCurrency":"EUR","counterCurrency":"USD","ratio":"1.25"}'; - $actualJson = json_encode(new CurrencyPair(new Currency('EUR'), new Currency('USD'), '1.25')); - - self::assertEquals($expectedJson, $actualJson); - } - - /** @test */ - public function it_parses_an_iso_string(): void + public function itProvidesGetters(): void { - self::assertEquals( - new CurrencyPair(new Currency('EUR'), new Currency('USD'), '1.250000'), - CurrencyPair::createFromIso('EUR/USD 1.250000') + $pair = new CurrencyPair( + new Currency('USD'), + new Currency('EUR'), + '1.0' ); + + self::assertEquals('USD', $pair->getBaseCurrency()->getCode()); + self::assertEquals('EUR', $pair->getCounterCurrency()->getCode()); + self::assertEquals('1.0', $pair->getConversionRatio()); } - /** @test */ - public function it_equals_to_another_currency_pair(): void + /** + * @test + */ + public function itProvidesEquality(): void { - $pair = new CurrencyPair( - new Currency('EUR'), + $pair1 = new CurrencyPair( new Currency('USD'), - '1.250000' + new Currency('EUR'), + '1.0' ); - self::assertFalse($pair->equals(new CurrencyPair( - new Currency('GBP'), + self::assertTrue($pair1->equals(new CurrencyPair( new Currency('USD'), - '1.250000' - ))); - - self::assertFalse($pair->equals(new CurrencyPair( new Currency('EUR'), - new Currency('GBP'), - '1.250000' + '1.0' ))); - - self::assertFalse($pair->equals(new CurrencyPair( - new Currency('EUR'), + self::assertFalse($pair1->equals(new CurrencyPair( new Currency('USD'), - '1.5000' + new Currency('EUR'), + '2.0' ))); + } - self::assertTrue($pair->equals(new CurrencyPair( - new Currency('EUR'), + /** + * @test + */ + public function itConvertsToJson(): void + { + $pair = new CurrencyPair( new Currency('USD'), - '1.250000' - ))); + new Currency('EUR'), + '1.0' + ); + + self::assertEquals('{"baseCurrency":"USD","counterCurrency":"EUR","ratio":"1.0"}', json_encode($pair)); + } + + /** + * @test + */ + public function itCanBeCreatedWithAnIsoString(): void + { + $pair = CurrencyPair::createFromIso('EUR/USD 1.2500'); + self::assertEquals('EUR', $pair->getBaseCurrency()->getCode()); + self::assertEquals('USD', $pair->getCounterCurrency()->getCode()); + self::assertEquals('1.2500', $pair->getConversionRatio()); } } diff --git a/tests/CurrencyTest.php b/tests/CurrencyTest.php index 8a07a0183..2500991ac 100644 --- a/tests/CurrencyTest.php +++ b/tests/CurrencyTest.php @@ -27,4 +27,21 @@ public function itAppliesUppercase(): void { self::assertEquals('USD', (new Currency('usd'))->getCode()); } + + /** + * @test + */ + public function itIsStringable(): void + { + self::assertEquals('USD', (string) new Currency('usd')); + } + + /** + * @test + */ + public function itProvidesEqualityComparison(): void + { + $currency = new Currency('usd'); + self::assertTrue($currency->equals(new Currency('USD'))); + } } diff --git a/tests/Exchange/FixedExchangeTest.php b/tests/Exchange/FixedExchangeTest.php new file mode 100644 index 000000000..b26891a6d --- /dev/null +++ b/tests/Exchange/FixedExchangeTest.php @@ -0,0 +1,33 @@ + ['USD' => '1.2500']]); + $currencyPair = $exchange->quote(new Currency('EUR'), new Currency('USD')); + + self::assertEquals('EUR', $currencyPair->getBaseCurrency()); + self::assertEquals('USD', $currencyPair->getCounterCurrency()); + self::assertEquals('1.2500', $currencyPair->getConversionRatio()); + } + + /** @test */ + public function it_throws_when_quoting_unknown_currency(): void + { + $this->expectException(UnresolvableCurrencyPairException::class); + $exchange = new FixedExchange(['EUR' => ['USD' => '1.2500']]); + $exchange->quote(new Currency('EUR'), new Currency('SOME')); + } +} diff --git a/tests/MoneyTest.php b/tests/MoneyTest.php index 1aa23d355..5c20ab942 100644 --- a/tests/MoneyTest.php +++ b/tests/MoneyTest.php @@ -400,6 +400,26 @@ public function itRoundsToUnit($amount, $unit, $expected, $roundingMode): void self::assertEquals(Money::EUR($expected), Money::EUR($amount)->roundToUnit($unit, $roundingMode)); } + /** @test */ + public function itThrowsWithDecimal(): void + { + $this->expectException(InvalidArgumentException::class); + new Money('5.1', new Currency(self::CURRENCY)); + } + + /** + * @test + */ + public function itThrowsWhenComparingDifferentCurrencies(): void + { + $money = new Money('5', new Currency(self::CURRENCY)); + + $this->expectException(InvalidArgumentException::class); + + /** @psalm-suppress UnusedMethodCall */ + $money->compare(new Money('5', new Currency('SOME'))); + } + /** * @psalm-return non-empty-list * * @psalm-suppress InvalidOperand + * @psalm-suppress LessSpecificReturnStatement + * @psalm-suppress MoreSpecificReturnType * * the {@see PHP_INT_MAX} operations below cannot be inferred to numeric-string * the {@see PHP_INT_MAX} operations below cannot be inferred to numeric-string