diff --git a/.horde.yml b/.horde.yml index ff9ecdd..fc14dee 100644 --- a/.horde.yml +++ b/.horde.yml @@ -32,7 +32,7 @@ license: uri: http://www.horde.org/licenses/lgpl21 dependencies: required: - php: ^7.4 || ^8 + php: ^8.1 composer: horde/exception: ^3 horde/util: ^3 diff --git a/composer.json b/composer.json index f1e784a..eac3bf7 100644 --- a/composer.json +++ b/composer.json @@ -30,8 +30,7 @@ "horde/util": "^3 || dev-FRAMEWORK_6_0" }, "require-dev": { - "horde/cli": "^3 || dev-FRAMEWORK_6_0", - "horde/test": "^3 || dev-FRAMEWORK_6_0" + "horde/cli": "^3 || dev-FRAMEWORK_6_0" }, "suggest": { "horde/cli": "^3 || dev-FRAMEWORK_6_0", @@ -55,4 +54,4 @@ "horde/horde-installer-plugin": true } } -} \ No newline at end of file +} diff --git a/lib/Horde/Text/Diff/Renderer/Inline.php b/lib/Horde/Text/Diff/Renderer/Inline.php index ada74d5..292860c 100644 --- a/lib/Horde/Text/Diff/Renderer/Inline.php +++ b/lib/Horde/Text/Diff/Renderer/Inline.php @@ -104,17 +104,37 @@ protected function _lines($lines, $prefix = ' ', $encode = true) protected function _added($lines) { array_walk($lines, array(&$this, '_encode')); - $lines[0] = $this->_ins_prefix . $lines[0]; - $lines[count($lines) - 1] .= $this->_ins_suffix; - return $this->_lines($lines, ' ', false); + + // If the last element is a newline marker, move it outside the tags + $trailing_newline = ''; + if (!empty($lines) && $lines[count($lines) - 1] === "\0") { + $trailing_newline = array_pop($lines); + } + + if (!empty($lines)) { + $lines[0] = $this->_ins_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_ins_suffix; + } + + return $this->_lines($lines, ' ', false) . $trailing_newline; } protected function _deleted($lines, $words = false) { array_walk($lines, array(&$this, '_encode')); - $lines[0] = $this->_del_prefix . $lines[0]; - $lines[count($lines) - 1] .= $this->_del_suffix; - return $this->_lines($lines, ' ', false); + + // If the last element is a newline marker, move it outside the tags + $trailing_newline = ''; + if (!empty($lines) && $lines[count($lines) - 1] === "\0") { + $trailing_newline = array_pop($lines); + } + + if (!empty($lines)) { + $lines[0] = $this->_del_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_del_suffix; + } + + return $this->_lines($lines, ' ', false) . $trailing_newline; } protected function _changed($orig, $final) @@ -176,10 +196,17 @@ protected function _splitOnWords($string, $newlineEscape = "\n") $pos = 0; while ($pos < $length) { - // Eat a word with any preceding whitespace. - $spaces = strspn(substr($string, $pos), " \n"); + // Check if we're at a newline - treat it as a separate token + if ($string[$pos] === "\n") { + $words[] = $newlineEscape; + $pos++; + continue; + } + + // Eat a word with any preceding whitespace (but not newlines). + $spaces = strspn(substr($string, $pos), " "); $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); - $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); + $words[] = substr($string, $pos, $spaces + $nextpos); $pos += $spaces + $nextpos; } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5efdf6f..0171c88 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,26 +1,13 @@ - - - - test - - - - - - src - - + + + + test + + + + + src + + diff --git a/src/InlineRenderer.php b/src/InlineRenderer.php index dd253a0..51755b3 100644 --- a/src/InlineRenderer.php +++ b/src/InlineRenderer.php @@ -112,17 +112,37 @@ protected function _lines(array $lines = [], string $prefix = ' ', $encode = tru protected function _added(array $lines = []): string { array_walk($lines, [&$this, '_encode']); - $lines[0] = $this->_ins_prefix . $lines[0]; - $lines[count($lines) - 1] .= $this->_ins_suffix; - return $this->_lines($lines, ' ', false); + + // If the last element is a newline marker, move it outside the tags + $trailing_newline = ''; + if (!empty($lines) && $lines[count($lines) - 1] === "\0") { + $trailing_newline = array_pop($lines); + } + + if (!empty($lines)) { + $lines[0] = $this->_ins_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_ins_suffix; + } + + return $this->_lines($lines, ' ', false) . $trailing_newline; } protected function _deleted(array $lines = []): string { array_walk($lines, [&$this, '_encode']); - $lines[0] = $this->_del_prefix . $lines[0]; - $lines[count($lines) - 1] .= $this->_del_suffix; - return $this->_lines($lines, ' ', false); + + // If the last element is a newline marker, move it outside the tags + $trailing_newline = ''; + if (!empty($lines) && $lines[count($lines) - 1] === "\0") { + $trailing_newline = array_pop($lines); + } + + if (!empty($lines)) { + $lines[0] = $this->_del_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_del_suffix; + } + + return $this->_lines($lines, ' ', false) . $trailing_newline; } protected function _changed(array $orig = [], array $final = []): string @@ -189,10 +209,17 @@ protected function _splitOnWords(string $string, string $newlineEscape = "\n") $pos = 0; while ($pos < $length) { - // Eat a word with any preceding whitespace. - $spaces = strspn(substr($string, $pos), " \n"); + // Check if we're at a newline - treat it as a separate token + if ($string[$pos] === "\n") { + $words[] = $newlineEscape; + $pos++; + continue; + } + + // Eat a word with any preceding whitespace (but not newlines). + $spaces = strspn(substr($string, $pos), " "); $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); - $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); + $words[] = substr($string, $pos, $spaces + $nextpos); $pos += $spaces + $nextpos; } diff --git a/src/NativeEngine.php b/src/NativeEngine.php index a413652..dd3f43e 100644 --- a/src/NativeEngine.php +++ b/src/NativeEngine.php @@ -260,8 +260,8 @@ protected function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) } $matches = $ymatches[$line]; foreach ($matches as $y) { - $k = $this->_lcsPos($y); if (empty($this->in_seq[$y])) { + $k = $this->_lcsPos($y); assert($k > 0); $ymids[$k] = $ymids[$k - 1]; break; diff --git a/test/EngineTest.php b/test/EngineTest.php index d921747..bdae999 100644 --- a/test/EngineTest.php +++ b/test/EngineTest.php @@ -10,16 +10,21 @@ use Horde\Text\Diff\ChangeOperation; use Horde\Text\Diff\CopyOperation; -use PHPUnit\Framework\TestCase; use Horde\Text\Diff\Diff; use Horde\Text\Diff\Exception; +use Horde\Text\Diff\NativeEngine; use Horde\Text\Diff\StringEngine; use Horde\Text\Diff\XdiffEngine; -use Horde\Text\Diff\NativeEngine; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; /** * @requires PHP >= 8.1 */ +#[CoversClass(Diff::class)] +#[CoversClass(NativeEngine::class)] +#[CoversClass(StringEngine::class)] +#[CoversClass(XdiffEngine::class)] class EngineTest extends TestCase { protected $_lines = array(); diff --git a/test/RendererTest.php b/test/RendererTest.php index c6c7d07..e6577b6 100644 --- a/test/RendererTest.php +++ b/test/RendererTest.php @@ -7,16 +7,23 @@ * @subpackage UnitTests */ namespace Horde\Text\Diff\Test; -use Horde\Test\TestCase as TestCase; + use Horde\Text\Diff\ContextRenderer; -use Horde\Text\Diff\Renderer; -use Horde\Text\Diff\NativeEngine; use Horde\Text\Diff\Diff; use Horde\Text\Diff\InlineRenderer; +use Horde\Text\Diff\NativeEngine; +use Horde\Text\Diff\Renderer; use Horde\Text\Diff\UnifiedRenderer; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + /** * @requires PHP >= 8.1 */ +#[CoversClass(ContextRenderer::class)] +#[CoversClass(InlineRenderer::class)] +#[CoversClass(Renderer::class)] +#[CoversClass(UnifiedRenderer::class)] class RendererTest extends TestCase { protected $_lines = array(); @@ -162,8 +169,7 @@ public function testPearBug4879() public function testPearBug4982() { - $this->markTestIncomplete('Still needs to be fixed.'); - /* wrong line breaks with inline renderer */ + /* Fixed: wrong line breaks with inline renderer */ $test = array(array('This line is different in 1.txt'), array('This is new !!', 'This line is different in 2.txt')); @@ -279,9 +285,7 @@ public function testPearBug7839() public function testPearBug12710() { - $this->markTestIncomplete(); - - /* failed assertion */ + /* Previously caused infinite recursion due to wrong order of operations in NativeEngine */ $a = <<The tax credit amounts to 30% of the cost of the system, with a maximum of 2,000. This credit is separate from the 500 home improvement @@ -300,7 +304,12 @@ class="anchor" title="Link to this section">
$diff = Diff::fromFileLineArrays(explode("\n", $a), explode("\n", $b), NativeEngine::class); $renderer = new InlineRenderer(); - $renderer->render($diff); + $result = $renderer->render($diff); + + // Should complete without hanging or crashing + $this->assertIsString($result); + $this->assertStringContainsString('', $result); + $this->assertStringContainsString('', $result); } public function testGithubPullRequest86() diff --git a/test/ThreeWayTest.php b/test/ThreeWayTest.php index 86cafae..be9288d 100644 --- a/test/ThreeWayTest.php +++ b/test/ThreeWayTest.php @@ -7,11 +7,15 @@ * @subpackage UnitTests */ namespace Horde\Text\Diff\Test; -use Horde\Test\TestCase as TestCase; + use Horde\Text\Diff\ThreeWay; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + /** * @requires PHP >= 8.1 */ +#[CoversClass(ThreeWay::class)] class ThreeWayTest extends TestCase { protected $_lines = array(); diff --git a/test/Unnamespaced/RendererTest.php b/test/Unnamespaced/RendererTest.php index 0b1bb24..3b39d1e 100644 --- a/test/Unnamespaced/RendererTest.php +++ b/test/Unnamespaced/RendererTest.php @@ -7,14 +7,20 @@ * @subpackage UnitTests */ namespace Horde\Text\Diff\Test\Unnamespaced; -use Horde\Test\TestCase as TestCase; -use \Horde_Text_Diff; -use \Horde_Text_Diff_Renderer_Context; -use \Horde_Text_Diff_Renderer_Inline; -use \Horde_Text_Diff_Renderer_Unified; -use \Horde_Text_Diff_Renderer; -use \Horde_Text_Diff_ThreeWay; +use Horde_Text_Diff; +use Horde_Text_Diff_Renderer; +use Horde_Text_Diff_Renderer_Context; +use Horde_Text_Diff_Renderer_Inline; +use Horde_Text_Diff_Renderer_Unified; +use Horde_Text_Diff_ThreeWay; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Horde_Text_Diff_Renderer::class)] +#[CoversClass(Horde_Text_Diff_Renderer_Context::class)] +#[CoversClass(Horde_Text_Diff_Renderer_Inline::class)] +#[CoversClass(Horde_Text_Diff_Renderer_Unified::class)] class RendererTest extends TestCase { protected $_lines = array(); @@ -161,8 +167,7 @@ public function testPearBug4879() public function testPearBug4982() { - $this->markTestIncomplete('Still needs to be fixed.'); - /* wrong line breaks with inline renderer */ + /* Fixed: wrong line breaks with inline renderer */ $test = array(array('This line is different in 1.txt'), array('This is new !!', 'This line is different in 2.txt')); @@ -297,9 +302,12 @@ class="anchor" title="Link to this section">
$diff = new Horde_Text_Diff('Native', array(explode("\n", $b), explode("\n", $a))); $renderer = new Horde_Text_Diff_Renderer_Inline(); - $renderer->render($diff); + $result = $renderer->render($diff); - $this->markTestIncomplete(); + // Should complete without hanging or crashing + $this->assertIsString($result); + $this->assertStringContainsString('', $result); + $this->assertStringContainsString('', $result); } public function testGithubPullRequest86() diff --git a/test/Unnamespaced/ThreeWayTest.php b/test/Unnamespaced/ThreeWayTest.php index a03ff1d..3ad7801 100644 --- a/test/Unnamespaced/ThreeWayTest.php +++ b/test/Unnamespaced/ThreeWayTest.php @@ -7,9 +7,12 @@ * @subpackage UnitTests */ namespace Horde\Text\Diff\Test\Unnamespaced; -use Horde\Test\TestCase as TestCase; -use \Horde_Text_Diff_ThreeWay; +use Horde_Text_Diff_ThreeWay; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Horde_Text_Diff_ThreeWay::class)] class ThreeWayTest extends TestCase { protected $_lines = array(); diff --git a/test/bootstrap.php b/test/bootstrap.php deleted file mode 100644 index c66dd7b..0000000 --- a/test/bootstrap.php +++ /dev/null @@ -1,15 +0,0 @@ -