Files
PhpSpreadsheet/tests/PhpSpreadsheetTests/Style/NumberFormat/Wizard/CurrencyTest.php
T
oleibman 684c677937 Document NumberFormat Wizard Dependency on Intl Extension
Intl is only a "suggested" extension. A lot of the NumberFormat Wizard code depends on it. That's insufficient reason to make it required, but the suggestion text now mentions this dependency explicitly. Also clean up the Wizard documentation to reflect some changes since PhpSpreadsheet 1.28.
2025-09-17 23:04:31 -07:00

200 lines
8.8 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Style\NumberFormat\Wizard;
use NumberFormatter;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Formatter;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Accounting;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Currency;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\CurrencyNegative;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard\Number;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;
class CurrencyTest extends TestCase
{
#[DataProvider('providerCurrency')]
public function testCurrency(
string $expectedResultPositive,
string $expectedResultNegative,
string $expectedResultZero,
string $currencyCode,
int $decimals,
bool $thousandsSeparator,
bool $currencySymbolPosition,
bool $currencySymbolSpacing,
CurrencyNegative $negative = CurrencyNegative::minus
): void {
$wizard = new Currency($currencyCode, $decimals, $thousandsSeparator, $currencySymbolPosition, $currencySymbolSpacing, negative: $negative);
self::assertSame($expectedResultPositive, Formatter::toFormattedString(1234.56, $wizard->format()));
self::assertSame($expectedResultNegative, Formatter::toFormattedString(-1234.56, $wizard->format()));
self::assertSame($expectedResultZero, Formatter::toFormattedString(0, $wizard->format()));
}
public static function providerCurrency(): array
{
return [
[' $1235 ', ' -$1235', ' $0 ', '$', 0, Number::WITHOUT_THOUSANDS_SEPARATOR, Currency::LEADING_SYMBOL, Currency::SYMBOL_WITH_SPACING],
[' $1,235 ', ' -$1,235', ' $0 ', '$', 0, Number::WITH_THOUSANDS_SEPARATOR, Currency::LEADING_SYMBOL, Currency::SYMBOL_WITH_SPACING],
[' $1,235 ', ' -$1,235', ' $0 ', '$', 0, Number::WITH_THOUSANDS_SEPARATOR, Currency::LEADING_SYMBOL, Currency::SYMBOL_WITHOUT_SPACING],
[' 1234.56€ ', ' -1234.56€ ', ' 0.00€ ', '€', 2, Number::WITHOUT_THOUSANDS_SEPARATOR, Currency::TRAILING_SYMBOL, Currency::SYMBOL_WITH_SPACING],
[' 1,234.56€ ', ' -1,234.56€ ', ' 0.00€ ', '€', 2, Number::WITH_THOUSANDS_SEPARATOR, Currency::TRAILING_SYMBOL, Currency::SYMBOL_WITH_SPACING],
[' 1234.56€ ', ' -1234.56€ ', ' 0.00€ ', '€', 2, Number::WITHOUT_THOUSANDS_SEPARATOR, Currency::TRAILING_SYMBOL, Currency::SYMBOL_WITHOUT_SPACING],
[' 1234.56€ ', ' (1234.56)€ ', ' 0.00€ ', '€', 2, Number::WITHOUT_THOUSANDS_SEPARATOR, Currency::TRAILING_SYMBOL, Currency::SYMBOL_WITHOUT_SPACING, CurrencyNegative::parentheses],
[' 1234.56 GBP ', ' (1234.56) GBP ', ' 0.00GBP ', 'GBP', 2, Number::WITHOUT_THOUSANDS_SEPARATOR, Currency::TRAILING_SYMBOL, Currency::SYMBOL_WITHOUT_SPACING, CurrencyNegative::parentheses],
];
}
public function testSetNegative(): void
{
$expectedResultNegative = ' (1234.56)€ ';
$currencyCode = '€';
$decimals = 2;
$thousandsSeparator = Number::WITHOUT_THOUSANDS_SEPARATOR;
$currencySymbolPosition = Currency::TRAILING_SYMBOL;
$currencySymbolSpacing = Currency::SYMBOL_WITHOUT_SPACING;
$negative = CurrencyNegative::parentheses;
$wizard = new Currency($currencyCode, $decimals, $thousandsSeparator, $currencySymbolPosition, $currencySymbolSpacing);
$wizard->setNegative($negative);
self::assertSame($expectedResultNegative, Formatter::toFormattedString(-1234.56, $wizard->format()));
}
#[DataProvider('providerCurrencyLocale')]
public function testCurrencyLocale(
string $expectedResult,
string $currencyCode,
string $locale,
?bool $stripRLM = null
): void {
if (class_exists(NumberFormatter::class) === false) {
self::markTestSkipped('Intl extension is not available');
}
$wizard = new Currency($currencyCode);
if ($stripRLM !== null) {
$wizard->setStripLeadingRLM($stripRLM);
}
$wizard->setLocale($locale);
self::assertSame($expectedResult, (string) $wizard);
}
public static function providerCurrencyLocale(): array
{
// \u{a0} is non-breaking space
// \u{200e} is LRM (left-to-right mark)
return [
["[\$€-fy-NL]\u{a0}#,##0.00;[\$€-fy-NL]\u{a0}#,##0.00-", '€', 'fy-NL'], // Trailing negative
["[\$€-nl-NL]\u{a0}#,##0.00;[\$€-nl-NL]\u{a0}-#,##0.00", '€', 'nl-NL'], // Sign between currency and value
["[\$€-nl-BE]\u{a0}#,##0.00;[\$€-nl-BE]\u{a0}-#,##0.00", '€', 'NL-BE'], // Sign between currency and value
["#,##0.00\u{a0}[\$€-fr-BE]", '€', 'fr-be'], // Trailing currency code
["#,##0.00\u{a0}[\$€-el-GR]", '€', 'el-gr'], // Trailing currency code
['[$$-en-CA]#,##0.00', '$', 'en-ca'],
["#,##0.00\u{a0}[\$\$-fr-CA]", '$', 'fr-ca'], // Trailing currency code
['[$¥-ja-JP]#,##0', '¥', 'ja-JP'], // No decimals
["#,##0.000\u{a0}[\$د.ب\u{200e}-ar-BH]", "د.ب\u{200e}", 'ar-BH', true], // 3 decimals
];
}
public function testIcu721(): void
{
if (class_exists(NumberFormatter::class) === false) {
self::markTestSkipped('Intl extension is not available');
}
$currencyCode = "د.ب\u{200e}";
$locale = 'ar-BH';
$wizardFalse = new Currency($currencyCode);
$wizardFalse->setStripLeadingRLM(false);
$wizardFalse->setLocale($locale);
$stringFalse = (string) $wizardFalse;
$wizardTrue = new Currency($currencyCode);
$wizardTrue->setStripLeadingRLM(true);
$wizardTrue->setLocale($locale);
$stringTrue = (string) $wizardTrue;
$version = Accounting::icuVersion();
if ($version < 72.1) {
self::assertSame($stringFalse, $stringTrue);
} else {
self::assertSame("\u{200f}$stringTrue", $stringFalse);
}
}
#[DataProvider('providerCurrencyLocaleNoDecimals')]
public function testCurrencyLocaleNoDecimals(
string $expectedResult,
string $currencyCode,
string $locale,
?bool $stripRLM = null
): void {
if (class_exists(NumberFormatter::class) === false) {
self::markTestSkipped('Intl extension is not available');
}
$wizard = new Currency($currencyCode, 0);
if ($stripRLM !== null) {
$wizard->setStripLeadingRLM($stripRLM);
}
$wizard->setLocale($locale);
self::assertSame($expectedResult, (string) $wizard);
}
public static function providerCurrencyLocaleNoDecimals(): array
{
// \u{a0} is non-breaking space
// \u{200e} is LRM (left-to-right mark)
return [
["[\$€-fy-NL]\u{a0}#,##0;[\$€-fy-NL]\u{a0}#,##0-", '€', 'fy-NL'], // Trailing negative
["[\$€-nl-NL]\u{a0}#,##0;[\$€-nl-NL]\u{a0}-#,##0", '€', 'nl-NL'], // Sign between currency and value
["[\$€-nl-BE]\u{a0}#,##0;[\$€-nl-BE]\u{a0}-#,##0", '€', 'NL-BE'], // Sign between currency and value
["#,##0\u{a0}[\$€-fr-BE]", '€', 'fr-be'], // Trailing currency code
["#,##0\u{a0}[\$€-el-GR]", '€', 'el-gr'], // Trailing currency code
['[$$-en-CA]#,##0', '$', 'en-ca'],
["#,##0\u{a0}[\$\$-fr-CA]", '$', 'fr-ca'], // Trailing currency code
['[$¥-ja-JP]#,##0', '¥', 'ja-JP'], // No decimals to truncate
["#,##0\u{a0}[\$د.ب\u{200e}-ar-BH]", "د.ب\u{200e}", 'ar-BH', true], // 3 decimals truncated to none
];
}
public function testCurrencyLocaleInvalidFormat(): void
{
if (class_exists(NumberFormatter::class) === false) {
self::markTestSkipped('Intl extension is not available');
}
$locale = 'en-usa';
$this->expectException(Exception::class);
$this->expectExceptionMessage("Invalid locale code '{$locale}'");
$wizard = new Currency('€');
$wizard->setLocale($locale);
}
public function testCurrencyLocaleInvalidCode(): void
{
if (class_exists(NumberFormatter::class) === false) {
self::markTestSkipped('Intl extension is not available');
}
$locale = 'nl-GB';
$this->expectException(Exception::class);
$this->expectExceptionMessage("Unable to read locale data for '{$locale}'");
$wizard = new Currency('€');
$wizard->setLocale($locale);
}
public function testLocaleNull3(): void
{
$wizard = new Currency('$', 2);
$reflectionMethod = new ReflectionMethod($wizard, 'formatCurrencyCode');
$result = $reflectionMethod->invokeArgs($wizard, []);
self::assertSame('$', $result);
}
}