Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions tests/BuffererWriterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

class BuffererWriterTest extends \PHPUnit\Framework\TestCase
{
private function getTmpFileName(): string
{
return tempnam(sys_get_temp_dir(), 'xlsxBufWriter');
}

public function testWriteAndClose(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file);

$writer->write('hello world');
$writer->close();

$this->assertSame('hello world', file_get_contents($file));
unlink($file);
}

public function testFtellReturnsPosition(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file);

$writer->write('hello');
$pos = $writer->ftell();

$this->assertSame(5, $pos);

$writer->close();
unlink($file);
}

public function testFtellReturnsNegativeOneWhenClosed(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file);

$writer->close();

$this->assertSame(-1, $writer->ftell());
unlink($file);
}

public function testFseekMovesPosition(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file);

$writer->write('hello world');
$result = $writer->fseek(0);

$this->assertSame(0, $result);

// Overwrite from beginning
$writer->write('HELLO');
$writer->close();

$this->assertSame('HELLO world', file_get_contents($file));
unlink($file);
}

public function testFseekReturnsNegativeOneWhenClosed(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file);

$writer->close();

$this->assertSame(-1, $writer->fseek(0));
unlink($file);
}

public function testPurgeWithUtf8CheckValid(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file, 'w', true);

$writer->write('valid UTF-8 string');
$writer->close();

$this->assertSame('valid UTF-8 string', file_get_contents($file));
unlink($file);
}

public function testPurgeWithUtf8CheckInvalid(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file, 'w', true);

// Write invalid UTF-8 bytes - this should trigger the error log
$capture = tmpfile();
$saved = ini_set('error_log', stream_get_meta_data($capture)['uri']);

$writer->write("\xC0\xAF invalid utf8");
$writer->close();

ini_set('error_log', $saved);

// The data should still be written despite the UTF-8 error
$this->assertSame("\xC0\xAF invalid utf8", file_get_contents($file));
unlink($file);
}

public function testPurgeLargeBuffer(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file);

// Write more than 8192 bytes to trigger automatic purge
$largeString = str_repeat('a', 9000);
$writer->write($largeString);
$writer->close();

$this->assertSame($largeString, file_get_contents($file));
unlink($file);
}

public function testDestructorClosesFile(): void
{
$file = $this->getTmpFileName();
$writer = new XLSXWriter_BuffererWriter($file);

$writer->write('destruct test');
unset($writer); // triggers __destruct

$this->assertSame('destruct test', file_get_contents($file));
unlink($file);
}
}
95 changes: 95 additions & 0 deletions tests/FinalizeSheetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

class FinalizeSheetTest extends \PHPUnit\Framework\TestCase
{
/** @var XLSXWriter */
private $writer;

public function setUp(): void
{
$this->writer = new TestXLSWriter();
}

public function testFinalizeSheetWithCustomOptions(): void
{
$this->writer->writeSheetHeader('Sheet1', ['col1' => 'string', 'col2' => 'integer']);
$this->writer->writeSheetRow('Sheet1', ['hello', 42]);

$this->writer->finalizeSheet('Sheet1', [
'orientation' => 'landscape',
'header' => '&LCustom Header',
'footer' => '&RPage &P',
'paperSize' => '9',
'margins' => [
'left' => '0.75',
'right' => '0.75',
'top' => '1.25',
'bottom' => '1.25',
'header' => '0.3',
'footer' => '0.3',
],
'printOptions' => [
'headings' => 'true',
'gridLines' => 'true',
],
]);

// Sheet should be finalized; writing to file should succeed
$fileName = tempnam(sys_get_temp_dir(), 'xlsxFinalize');
$this->writer->writeToFile($fileName);
$this->assertFileExists($fileName);
$this->assertGreaterThan(0, filesize($fileName));
unlink($fileName);
}

public function testFinalizeSheetEmptyNameIsNoop(): void
{
$this->writer->writeSheetRow('Sheet1', ['test']);

// Finalize with empty name should not error
$this->writer->finalizeSheet('');

$fileName = tempnam(sys_get_temp_dir(), 'xlsxFinalize');
$this->writer->writeToFile($fileName);
$this->assertFileExists($fileName);
unlink($fileName);
}

public function testFinalizeSheetCalledTwiceIsIdempotent(): void
{
$this->writer->writeSheetHeader('Sheet1', ['col1' => 'string']);
$this->writer->writeSheetRow('Sheet1', ['data']);

$this->writer->finalizeSheet('Sheet1');
$this->writer->finalizeSheet('Sheet1'); // second call should be no-op

$fileName = tempnam(sys_get_temp_dir(), 'xlsxFinalize');
$this->writer->writeToFile($fileName);
$this->assertFileExists($fileName);
unlink($fileName);
}

public function testFinalizeSheetPartialOptionsUsesDefaults(): void
{
$this->writer->writeSheetHeader('Sheet1', ['col1' => 'string']);
$this->writer->writeSheetRow('Sheet1', ['value']);

// Only override orientation, rest should use defaults
$this->writer->finalizeSheet('Sheet1', [
'orientation' => 'landscape',
]);

$fileName = tempnam(sys_get_temp_dir(), 'xlsxFinalize');
$this->writer->writeToFile($fileName);
$this->assertFileExists($fileName);
unlink($fileName);
}

/**
* @phan-suppress PhanTypeObjectUnsetDeclaredProperty
*/
public function tearDown(): void
{
unset($this->writer);
}
}
167 changes: 167 additions & 0 deletions tests/XLSXWriterStaticMethodsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php

class XLSXWriterStaticMethodsTest extends \PHPUnit\Framework\TestCase
{
// --- xlsCell ---

public function testXlsCellRelative(): void
{
$this->assertSame('A1', XLSXWriter::xlsCell(0, 0));
$this->assertSame('B2', XLSXWriter::xlsCell(1, 1));
$this->assertSame('Z1', XLSXWriter::xlsCell(0, 25));
$this->assertSame('AA1', XLSXWriter::xlsCell(0, 26));
}

public function testXlsCellAbsolute(): void
{
$this->assertSame('$A$1', XLSXWriter::xlsCell(0, 0, true));
$this->assertSame('$C$5', XLSXWriter::xlsCell(4, 2, true));
$this->assertSame('$AA$1', XLSXWriter::xlsCell(0, 26, true));
}

// --- sanitize_sheetname ---

public function testSanitizeSheetnameRemovesBadChars(): void
{
$this->assertSame('clean', XLSXWriter::sanitize_sheetname('clean'));
$this->assertSame('a b c d e f g', XLSXWriter::sanitize_sheetname('a\\b/c?d*e:f[g]'));
}

public function testSanitizeSheetnameTruncatesTo31Chars(): void
{
$long = str_repeat('a', 50);
$result = XLSXWriter::sanitize_sheetname($long);
$this->assertSame(31, strlen($result));
}

public function testSanitizeSheetnameTrimsQuotes(): void
{
$this->assertSame('Sheet', XLSXWriter::sanitize_sheetname("'Sheet'"));
}

public function testSanitizeSheetnameEmptyReturnsFallback(): void
{
$result = XLSXWriter::sanitize_sheetname('');
$this->assertRegExp('/^Sheet\d{3}$/', $result);
}

// --- log ---

public function testLogStringMessage(): void
{
$capture = tmpfile();
$saved = ini_set('error_log', stream_get_meta_data($capture)['uri']);

XLSXWriter::log('Test log message');

ini_set('error_log', $saved);

rewind($capture);
$output = stream_get_contents($capture);
$this->assertStringContainsString('Test log message', $output);
}

public function testLogArrayMessage(): void
{
$capture = tmpfile();
$saved = ini_set('error_log', stream_get_meta_data($capture)['uri']);

XLSXWriter::log(['key' => 'value']);

ini_set('error_log', $saved);

rewind($capture);
$output = stream_get_contents($capture);
$this->assertStringContainsString('"key":"value"', $output);
}

// --- add_to_list_get_index ---

public function testAddToListGetIndexNewItem(): void
{
$list = ['a', 'b', 'c'];
$idx = XLSXWriter::add_to_list_get_index($list, 'd');
$this->assertSame(3, $idx);
$this->assertCount(4, $list);
$this->assertSame('d', $list[3]);
}

public function testAddToListGetIndexExistingItem(): void
{
$list = ['a', 'b', 'c'];
$idx = XLSXWriter::add_to_list_get_index($list, 'b');
$this->assertSame(1, $idx);
$this->assertCount(3, $list); // no duplicate added
}

// --- convert_date_time ---

public function testConvertDateTimeStandardDate(): void
{
// 2018-12-31 should give a known Excel serial number
$result = XLSXWriter::convert_date_time('2018-12-31');
$this->assertSame(43465.0, (float)$result);
}

public function testConvertDateTimeWithTime(): void
{
$result = XLSXWriter::convert_date_time('2018-12-31 12:00:00');
$this->assertEqualsWithDelta(43465.5, $result, 0.0001);
}

public function testConvertDateTimeOnlyTime(): void
{
// Time-only: no date regex match, year/month/day stay 0, boundary check is skipped
$result = XLSXWriter::convert_date_time('12:00:00');
// When no date part, days calculation gives a negative result; just verify it runs
$this->assertIsNumeric($result);

// Use the 1899-12-31 epoch date to get pure time
$result2 = XLSXWriter::convert_date_time('1899-12-31 12:00:00');
$this->assertEqualsWithDelta(0.5, $result2, 0.0001);
}

public function testConvertDateTimeExcelEpoch1900(): void
{
// 1899-12-31 => seconds only (epoch)
$this->assertSame(0.0, (float)XLSXWriter::convert_date_time('1899-12-31'));
}

public function testConvertDateTimeExcelFalseLeapday(): void
{
// 1900-02-29 => 60
$result = XLSXWriter::convert_date_time('1900-02-29');
$this->assertSame(60.0, (float)$result);
}

public function testConvertDateTimeInvalidYear(): void
{
// Year before 1900 epoch
$this->assertSame(0, XLSXWriter::convert_date_time('1800-01-01'));
}

public function testConvertDateTimeInvalidMonth(): void
{
$this->assertSame(0, XLSXWriter::convert_date_time('2020-13-01'));
}

public function testConvertDateTimeInvalidDay(): void
{
$this->assertSame(0, XLSXWriter::convert_date_time('2020-02-30'));
}

public function testConvertDateTimeLeapYear(): void
{
// 2000 is a leap year (divisible by 400)
$result = XLSXWriter::convert_date_time('2000-02-29');
$this->assertGreaterThan(0, $result);
}

public function testConvertDateTimeNonLeapCentury(): void
{
// 1900 is NOT a leap year (divisible by 100 but not 400)
// But Excel treats it as one, so 1900-02-29 is the special case
// 2100-02-29 should return 0 (invalid day for non-leap century)
$this->assertSame(0, XLSXWriter::convert_date_time('2100-02-29'));
}
}
Loading