Skip to content

Commit 983be5f

Browse files
committed
Fix
1 parent cafeb88 commit 983be5f

4 files changed

Lines changed: 135 additions & 11 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@ on:
88

99
jobs:
1010
check:
11-
name: PHP 8.2 — Tests
11+
name: PHP ${{ matrix.php }} — Tests
1212
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
php: ['8.2', '8.3', '8.4', '8.5']
1317

1418
steps:
1519
- uses: actions/checkout@v4
1620

1721
- name: Setup PHP
1822
uses: shivammathur/setup-php@v2
1923
with:
20-
php-version: '8.2'
24+
php-version: ${{ matrix.php }}
2125
extensions: mbstring, json
2226

2327
- name: Install dependencies

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![CI](https://github.com/MADEVAL/ReadSight/actions/workflows/ci.yml/badge.svg)](https://github.com/MADEVAL/ReadSight/actions/workflows/ci.yml)
44
[![PHP](https://img.shields.io/badge/PHP-%3E%3D%208.2-777bb3?logo=php)](https://www.php.net/)
55
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6-
[![Tests](https://img.shields.io/badge/tests-184%20passed-brightgreen)](https://github.com/MADEVAL/ReadSight)
6+
[![Tests](https://img.shields.io/badge/tests-189%20passed-brightgreen)](https://github.com/MADEVAL/ReadSight)
77
[![PHPStan](https://img.shields.io/badge/PHPStan-level%20max-brightgreen)](https://phpstan.org/)
88
[![Languages](https://img.shields.io/badge/languages-79-9cf)](https://github.com/MADEVAL/ReadSight)
99
[![Formulas](https://img.shields.io/badge/formulas-17-orange)](https://github.com/MADEVAL/ReadSight)
@@ -265,7 +265,7 @@ Engine (facade)
265265
```bash
266266
composer install # Install dependencies
267267

268-
composer test # Run PHPUnit (167 tests)
268+
composer test # Run PHPUnit (189 tests)
269269
composer test:coverage # With HTML coverage report
270270
composer analyse # PHPStan level max
271271
composer cs:check # PHP CS Fixer (dry-run)
@@ -277,8 +277,8 @@ composer check # All checks: CS + PHPStan + Tests
277277

278278
| Metric | Value |
279279
|---|---|
280-
| Tests | **183** |
281-
| Assertions | **451** |
280+
| Tests | **189** |
281+
| Assertions | **982** |
282282
| PHPStan | **Level max, 0 errors** |
283283
| PHP | 8.5.4 |
284284
| Source classes | 40 |

tests/Regression/SyllableConsistencyTest.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,114 @@ public function test_analyze_metrics_are_consistent(): void
142142
$this->assertGreaterThan(0, $stats->letterCount);
143143
$this->assertGreaterThan(0, $stats->sentenceCount);
144144
}
145+
146+
// --- Property-based tests (invariant checks across many inputs) ---
147+
148+
public function test_fuzz_syllable_count_in_bounds(): void
149+
{
150+
$engine = new Engine('en-us', self::DATA_DIR . '/patterns', self::DATA_DIR . '/languages');
151+
152+
$words = [
153+
'', 'a', 'i', 'the', 'cat', 'dog', 'run', 'go', 'be', 'no',
154+
'table', 'apple', 'house', 'water', 'light', 'world', 'phone',
155+
'banana', 'computer', 'elephant', 'umbrella', 'beautiful', 'tomorrow',
156+
'information', 'university', 'extraordinary', 'communicate', 'relationship',
157+
'revolutionary', 'internationalization', 'uncharacteristically',
158+
'antidisestablishmentarianism', 'pneumonoultramicroscopicsilicovolcanoconiosis',
159+
];
160+
161+
foreach ($words as $word) {
162+
$wordLength = \mb_strlen($word);
163+
$count = $engine->syllableCount($word);
164+
165+
if ($wordLength === 0) {
166+
$this->assertSame(0, $count, "Empty word must have 0 syllables");
167+
} else {
168+
$this->assertGreaterThanOrEqual(1, $count, "Word '{$word}' has {$count} syllables (expected >=1)");
169+
$this->assertLessThanOrEqual($wordLength, $count, "Word '{$word}' has {$count} syllables (expected <= {$wordLength})");
170+
}
171+
}
172+
}
173+
174+
public function test_fuzz_split_word_reconstructs_original(): void
175+
{
176+
$engine = new Engine('en-us', self::DATA_DIR . '/patterns', self::DATA_DIR . '/languages');
177+
178+
$words = [
179+
'a', 'the', 'cat', 'dog', 'table', 'apple', 'house', 'water', 'light',
180+
'banana', 'computer', 'elephant', 'umbrella', 'beautiful', 'tomorrow',
181+
'information', 'university', 'extraordinary', 'communicate',
182+
'revolutionary', 'international', 'circumstance', 'accommodation',
183+
];
184+
185+
foreach ($words as $word) {
186+
$parts = $engine->splitWord($word);
187+
$reconstructed = \implode('', $parts);
188+
$this->assertSame($word, $reconstructed, "Word '{$word}' not reconstructed from parts: " . \implode('-', $parts));
189+
$this->assertCount($engine->syllableCount($word), $parts, "Word '{$word}' syllable count mismatch");
190+
}
191+
}
192+
193+
public function test_fuzz_consistent_across_repeated_calls(): void
194+
{
195+
$engine = new Engine('en-us', self::DATA_DIR . '/patterns', self::DATA_DIR . '/languages');
196+
197+
$words = [
198+
'the', 'cat', 'table', 'banana', 'computer', 'elephant', 'beautiful',
199+
'university', 'extraordinary', 'communication',
200+
];
201+
202+
for ($i = 0; $i < 5; $i++) {
203+
foreach ($words as $word) {
204+
$parts1 = $engine->splitWord($word);
205+
$parts2 = $engine->splitWord($word);
206+
$this->assertSame($parts1, $parts2, "Word '{$word}' split inconsistently on iteration {$i}");
207+
$this->assertSame($engine->syllableCount($word), \count($parts1), "Word '{$word}' count mismatch on iteration {$i}");
208+
}
209+
}
210+
}
211+
212+
public function test_fuzz_empty_and_edge_cases(): void
213+
{
214+
$engine = new Engine('en-us', self::DATA_DIR . '/patterns', self::DATA_DIR . '/languages');
215+
216+
$this->assertSame(0, $engine->syllableCount(''));
217+
$this->assertSame([], $engine->splitWord(''));
218+
219+
$singleChars = ['a', 'b', 'c', 'z', 'A', 'Z'];
220+
foreach ($singleChars as $char) {
221+
$this->assertSame(1, $engine->syllableCount($char), "Char '{$char}'");
222+
$this->assertSame([$char], $engine->splitWord($char), "Char '{$char}' split");
223+
}
224+
}
225+
226+
public function test_fuzz_multilingual_invariants(): void
227+
{
228+
$langWords = [
229+
'en-us' => ['the', 'table', 'banana', 'computer', 'university'],
230+
'ru' => ['а', 'мы', 'слово', 'молоко', 'красивый'],
231+
'de-1996' => ['in', 'und', 'Schreiben', 'Verständlichkeit'],
232+
'fr' => ['le', 'bonjour', 'ordinateur'],
233+
'es' => ['y', 'hola', 'hermosa', 'computadora'],
234+
'it' => ['e', 'ciao', 'bellissimo', 'università'],
235+
'nl' => ['de', 'goed', 'morgen'],
236+
'pt' => ['de', 'obrigado', 'computador'],
237+
'tr' => ['ve', 'merhaba', 'güzel'],
238+
'pl' => ['i', 'dzień', 'piękny'],
239+
];
240+
241+
foreach ($langWords as $lang => $words) {
242+
$engine = new Engine($lang, self::DATA_DIR . '/patterns', self::DATA_DIR . '/languages');
243+
foreach ($words as $word) {
244+
$parts = $engine->splitWord($word);
245+
$reconstructed = \implode('', $parts);
246+
$this->assertSame($word, $reconstructed, "Lang {$lang}: '{$word}' not reconstructed");
247+
248+
$count = $engine->syllableCount($word);
249+
$this->assertGreaterThanOrEqual(1, $count, "Lang {$lang}: '{$word}' has 0 syllables");
250+
$this->assertLessThanOrEqual(\mb_strlen($word), $count, "Lang {$lang}: '{$word}' has too many syllables");
251+
$this->assertCount($count, $parts, "Lang {$lang}: '{$word}' count != parts");
252+
}
253+
}
254+
}
145255
}

tests/Unit/Hyphenation/JsonPatternCacheTest.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,23 @@ protected function setUp(): void
2525

2626
protected function tearDown(): void
2727
{
28-
$files = \glob($this->cacheDir . '/*.json') ?: [];
29-
foreach ($files as $file) {
30-
\unlink($file);
31-
}
3228
if (\is_dir($this->cacheDir)) {
33-
\rmdir($this->cacheDir);
29+
$this->deleteDir($this->cacheDir);
30+
}
31+
}
32+
33+
private function deleteDir(string $dir): void
34+
{
35+
$items = \array_diff((array) \scandir($dir), ['.', '..']);
36+
foreach ($items as $item) {
37+
$path = $dir . \DIRECTORY_SEPARATOR . $item;
38+
if (\is_dir($path)) {
39+
$this->deleteDir($path);
40+
} else {
41+
\unlink($path);
42+
}
3443
}
44+
\rmdir($dir);
3545
}
3646

3747
public function test_has_returns_false_initially(): void

0 commit comments

Comments
 (0)