Files
oleibman 057572ee90 Change Additional Statistical Tests to Use Spreadsheet Context (#3217)
* Change Additional Statistical Tests to Use Spreadsheet Context

With an earlier change, I made all but 18 Statistical tests run in spreadsheet context. This PR changes 12 of those 18. The remaining 6 usually return array results, so it is a tougher task to handle them. I will continue to think on it.

AVERAGEIF, AVERAGEIFS, and COUNTBLANK are changed to throw an Exception when a range is specified as a literal. They previously accepted array (enclosed in braces) literals, and bumbled along till they threw an error for non-array literals. Throwing an exception appears to be analogous to how Excel operates, rather than something more friendly like a VALUE error. There may be other functions which require similar treatment.

There also remains a TODO for COUNTIFS, and possibly other functions. It appears that PhpSpreadsheet counts booleans for both integer and string compares and probably shouldn't. Again, this is a problem for another day.

* Scrutinizer

Fix one problem.

* Scrutinizer Ignores Its Own Suggested Remedy

Try another approach.
2022-12-02 14:14:43 -08:00

247 lines
7.6 KiB
PHP

<?php
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PHPUnit\Framework\TestCase;
class AllSetupTeardown extends TestCase
{
/**
* @var string
*/
private $compatibilityMode;
/**
* @var ?Spreadsheet
*/
private $spreadsheet;
/**
* @var ?Worksheet
*/
private $sheet;
protected function setUp(): void
{
$this->compatibilityMode = Functions::getCompatibilityMode();
}
protected function tearDown(): void
{
Functions::setCompatibilityMode($this->compatibilityMode);
$this->sheet = null;
if ($this->spreadsheet !== null) {
$this->spreadsheet->disconnectWorksheets();
$this->spreadsheet = null;
}
}
protected static function setOpenOffice(): void
{
Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE);
}
protected static function setGnumeric(): void
{
Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC);
}
/**
* @param mixed $expectedResult
*/
protected function mightHaveException($expectedResult): void
{
if ($expectedResult === 'exception') {
$this->expectException(CalcException::class);
}
}
/**
* @param mixed $value
*/
protected function setCell(string $cell, $value): void
{
if ($value !== null) {
if (is_string($value) && is_numeric($value)) {
$this->getSheet()->getCell($cell)->setValueExplicit($value, DataType::TYPE_STRING);
} else {
$this->getSheet()->getCell($cell)->setValue($value);
}
}
}
protected function getSpreadsheet(): Spreadsheet
{
if ($this->spreadsheet !== null) {
return $this->spreadsheet;
}
$this->spreadsheet = new Spreadsheet();
return $this->spreadsheet;
}
protected function getSheet(): Worksheet
{
if ($this->sheet !== null) {
return $this->sheet;
}
$this->sheet = $this->getSpreadsheet()->getActiveSheet();
return $this->sheet;
}
/**
* Excel handles text/logical/empty cells differently when
* passed directly as arguments as opposed to cell references or arrays.
* This function will test both approaches.
*
* @param mixed $expectedResult
* @param array $args
*/
protected function runTestCases(string $functionName, $expectedResult, ...$args): void
{
if (is_array($expectedResult)) {
$this->runTestCaseReference($functionName, $expectedResult[0], ...$args);
$this->runTestCaseDirect($functionName, $expectedResult[1], ...$args);
} else {
$this->runTestCaseReference($functionName, $expectedResult, ...$args);
$this->runTestCaseDirect($functionName, $expectedResult, ...$args);
}
}
/**
* Excel handles text/logical/empty cells differently when
* passed directly as arguments as opposed to cell references or arrays.
* This functions tests passing as arrays.
*
* @param mixed $expectedResult
* @param array $args
*/
protected function runTestCaseReference(string $functionName, $expectedResult, ...$args): void
{
$this->mightHaveException($expectedResult);
$sheet = $this->getSheet();
$formula = "=$functionName(";
$comma = '';
$row = 0;
foreach ($args as $arg) {
++$row;
if (is_array($arg)) {
$arrayArg = '{';
$arrayComma = '';
foreach ($arg as $arrayItem) {
$arrayArg .= $arrayComma;
$arrayArg .= $this->convertToString($arrayItem);
$arrayComma = ';';
}
$arrayArg .= '}';
$formula .= "$comma$arrayArg";
$comma = ',';
} else { // @phpstan-ignore-line
$cellId = "A$row";
$formula .= "$comma$cellId";
$comma = ',';
$this->setCell($cellId, $arg);
}
}
$formula .= ')';
$this->setCell('B1', $formula);
self::assertEqualsWithDelta($expectedResult, $sheet->getCell('B1')->getCalculatedValue(), 1.0e-8, 'arguments supplied as references');
}
/**
* Excel handles text/logical/empty cells differently when
* passed directly as arguments as opposed to cell references or arrays.
* This functions tests passing as direct arguments.
*
* @param mixed $expectedResult
* @param array $args
*/
protected function runTestCaseDirect(string $functionName, $expectedResult, ...$args): void
{
$this->mightHaveException($expectedResult);
$sheet = $this->getSheet();
$formula = "=$functionName(";
$comma = '';
foreach ($args as $arg) {
if (is_array($arg)) {
foreach ($arg as $arrayItem) {
$formula .= $comma;
$comma = ',';
$formula .= $this->convertToString($arrayItem);
}
} else { // @phpstan-ignore-line
$formula .= $comma;
$comma = ',';
$formula .= $this->convertToString($arg);
}
}
$formula .= ')';
$this->setCell('B2', $formula);
self::assertEqualsWithDelta($expectedResult, $sheet->getCell('B2')->getCalculatedValue(), 1.0e-8, 'arguments supplied directly');
}
/**
* Excel seems to reject bracket notation for literal arrays
* for some functions.
*
* @param mixed $expectedResult
* @param array $args
*/
protected function runTestCaseNoBracket(string $functionName, $expectedResult, ...$args): void
{
$this->mightHaveException($expectedResult);
$sheet = $this->getSheet();
$formula = "=$functionName(";
$comma = '';
$row = 0;
foreach ($args as $arg) {
++$row;
if (is_array($arg)) {
$col = 'A';
$arrayRange = '';
foreach ($arg as $arrayItem) {
$cellId = "$col$row";
$arrayRange = "A$row:$cellId";
$this->setCell($cellId, $arrayItem);
++$col;
}
$formula .= "$comma$arrayRange";
$comma = ',';
} else { // @phpstan-ignore-line
$cellId = "A$row";
$formula .= "$comma$cellId";
$comma = ',';
if (is_string($arg) && substr($arg, 0, 1) === '=') {
$sheet->getCell($cellId)->setValueExplicit($arg, DataType::TYPE_STRING);
} else {
$this->setCell($cellId, $arg);
}
}
}
$formula .= ')';
$this->setCell('Z99', $formula);
self::assertEqualsWithDelta($expectedResult, $sheet->getCell('Z99')->getCalculatedValue(), 1.0e-8, 'arguments supplied as ranges');
}
/**
* @param mixed $arg
*/
private function convertToString($arg): string
{
if (is_string($arg)) {
return '"' . $arg . '"';
}
if (is_bool($arg)) {
return $arg ? 'TRUE' : 'FALSE';
}
return (string) $arg;
}
}