From 4280233b403467e5d34723d3e8c2036a92200b9e Mon Sep 17 00:00:00 2001 From: nuno maduro Date: Sat, 2 May 2026 18:37:24 +0100 Subject: [PATCH] wip --- tests/Unit/Plugins/Tia/ContentHash.php | 261 +++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 tests/Unit/Plugins/Tia/ContentHash.php diff --git a/tests/Unit/Plugins/Tia/ContentHash.php b/tests/Unit/Plugins/Tia/ContentHash.php new file mode 100644 index 00000000..d1b2087b --- /dev/null +++ b/tests/Unit/Plugins/Tia/ContentHash.php @@ -0,0 +1,261 @@ +toBeFalse(); + }); + + it('hashes an existing file', function () { + $path = tempnam(sys_get_temp_dir(), 'pest_').'.php'; + file_put_contents($path, "toBeString()->not->toBeEmpty(); + } finally { + @unlink($path); + } + }); +}); + +describe('PHP files', function () { + it('produces the same hash regardless of whitespace differences', function () { + $a = ContentHash::ofContent('a.php', "toBe($b); + }); + + it('ignores single-line comments', function () { + $a = ContentHash::ofContent('a.php', "toBe($b); + }); + + it('ignores hash-style comments', function () { + $a = ContentHash::ofContent('a.php', "toBe($b); + }); + + it('ignores multi-line comments', function () { + $a = ContentHash::ofContent('a.php', "toBe($b); + }); + + it('ignores doc comments', function () { + $a = ContentHash::ofContent('a.php', "toBe($b); + }); + + it('detects code changes', function () { + $a = ContentHash::ofContent('a.php', "not->toBe($b); + }); + + it('preserves whitespace inside string literals', function () { + $a = ContentHash::ofContent('a.php', "not->toBe($b); + }); + + it('treats variable renames as a change', function () { + $a = ContentHash::ofContent('a.php', "not->toBe($b); + }); + + it('falls back to a raw hash for unparseable PHP', function () { + $hash = ContentHash::ofContent('a.php', 'not valid php at all'); + + expect($hash)->toBeString()->not->toBeEmpty(); + }); + + it('is case-insensitive on the file extension', function () { + $a = ContentHash::ofContent('a.PHP', "toBe($b); + }); +}); + +describe('Blade files', function () { + it('strips blade comments', function () { + $a = ContentHash::ofContent('a.blade.php', "
{{-- a comment --}}Hello
"); + $b = ContentHash::ofContent('a.blade.php', "
Hello
"); + + expect($a)->toBe($b); + }); + + it('strips multi-line blade comments', function () { + $a = ContentHash::ofContent('a.blade.php', "
\n{{--\n multi\n line\n--}}\nHello\n
"); + $b = ContentHash::ofContent('a.blade.php', "
Hello
"); + + expect($a)->toBe($b); + }); + + it('collapses whitespace', function () { + $a = ContentHash::ofContent('a.blade.php', "
\n Hello\n World\n
"); + $b = ContentHash::ofContent('a.blade.php', "
Hello World
"); + + expect($a)->toBe($b); + }); + + it('detects content changes', function () { + $a = ContentHash::ofContent('a.blade.php', "
Hello
"); + $b = ContentHash::ofContent('a.blade.php', "
Goodbye
"); + + expect($a)->not->toBe($b); + }); + + it('keeps blade directives intact', function () { + $a = ContentHash::ofContent('a.blade.php', "@if(\$user)Hi @endif"); + $b = ContentHash::ofContent('a.blade.php', "@if(\$user)Bye @endif"); + + expect($a)->not->toBe($b); + }); + + it('does not use the PHP tokenizer for blade files', function () { + $a = ContentHash::ofContent('a.blade.php', " hello"); + $b = ContentHash::ofContent('a.blade.php', " hello"); + + expect($a)->not->toBe($b); + }); +}); + +describe('JavaScript-like files', function () { + it('strips line comments', function () { + $a = ContentHash::ofContent('a.js', "// a comment\nconst foo = 1;"); + $b = ContentHash::ofContent('a.js', "const foo = 1;"); + + expect($a)->toBe($b); + }); + + it('strips block comments on their own lines', function () { + $a = ContentHash::ofContent('a.js', "/* block */\nconst foo = 1;"); + $b = ContentHash::ofContent('a.js', "const foo = 1;"); + + expect($a)->toBe($b); + }); + + it('collapses whitespace', function () { + $a = ContentHash::ofContent('a.js', "const foo = 1;\n\nconst bar = 2;"); + $b = ContentHash::ofContent('a.js', "const foo = 1; const bar = 2;"); + + expect($a)->toBe($b); + }); + + it('detects code changes', function () { + $a = ContentHash::ofContent('a.js', "const foo = 1;"); + $b = ContentHash::ofContent('a.js', "const foo = 2;"); + + expect($a)->not->toBe($b); + }); + + it('does not strip inline trailing comments', function () { + $a = ContentHash::ofContent('a.js', "const foo = 1; // inline"); + $b = ContentHash::ofContent('a.js', "const foo = 1;"); + + expect($a)->not->toBe($b); + }); + + it('applies the same rules to .ts files', function () { + $a = ContentHash::ofContent('a.ts', "// comment\nconst foo: number = 1;"); + $b = ContentHash::ofContent('a.ts', "const foo: number = 1;"); + + expect($a)->toBe($b); + }); + + it('applies the same rules to .tsx files', function () { + $a = ContentHash::ofContent('a.tsx', "// comment\nconst Foo = () =>
;"); + $b = ContentHash::ofContent('a.tsx', "const Foo = () =>
;"); + + expect($a)->toBe($b); + }); + + it('applies the same rules to .jsx files', function () { + $a = ContentHash::ofContent('a.jsx', "// comment\nconst Foo = () =>
;"); + $b = ContentHash::ofContent('a.jsx', "const Foo = () =>
;"); + + expect($a)->toBe($b); + }); + + it('applies the same rules to .vue files', function () { + $a = ContentHash::ofContent('a.vue', ""); + $b = ContentHash::ofContent('a.vue', ""); + + expect($a)->toBe($b); + }); + + it('applies the same rules to .svelte files', function () { + $a = ContentHash::ofContent('a.svelte', ""); + $b = ContentHash::ofContent('a.svelte', ""); + + expect($a)->toBe($b); + }); + + it('applies the same rules to .mjs, .cjs, and .mts files', function () { + foreach (['mjs', 'cjs', 'mts'] as $ext) { + $a = ContentHash::ofContent("a.$ext", "// comment\nexport const foo = 1;"); + $b = ContentHash::ofContent("a.$ext", "export const foo = 1;"); + + expect($a)->toBe($b); + } + }); +}); + +describe('unknown extensions', function () { + it('hashes the raw content for unknown extensions', function () { + $a = ContentHash::ofContent('a.txt', "hello world"); + $b = ContentHash::ofContent('a.txt', "hello world"); + + expect($a)->toBe($b); + }); + + it('does not normalise whitespace for unknown extensions', function () { + $a = ContentHash::ofContent('a.txt', "hello world"); + $b = ContentHash::ofContent('a.txt', "hello world"); + + expect($a)->not->toBe($b); + }); + + it('does not strip comments for unknown extensions', function () { + $a = ContentHash::ofContent('a.txt', "// not a comment here\nhello"); + $b = ContentHash::ofContent('a.txt', "hello"); + + expect($a)->not->toBe($b); + }); + + it('hashes files with no extension as raw content', function () { + $a = ContentHash::ofContent('Makefile', "all:\n\techo hi"); + $b = ContentHash::ofContent('Makefile', "all:\n\techo hi"); + + expect($a)->toBe($b); + }); +}); + +describe('output format', function () { + it('returns a 32-character hex xxh128 hash', function () { + $hash = ContentHash::ofContent('a.php', 'toMatch('/^[a-f0-9]{32}$/'); + }); + + it('returns a stable hash for empty content', function () { + $a = ContentHash::ofContent('a.php', ''); + $b = ContentHash::ofContent('a.php', ''); + + expect($a)->toBe($b); + }); +});