Add bin/ scripts to PHPStan scanFiles, suppress property.onlyWritten for by-ref test properties, add exhaustive default match arm, use explicit expects() on mock stubs, fix data provider return type, and simplify redundant null check in NotificationServiceTest. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
128 lines
4.2 KiB
PHP
128 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Tests\Architecture;
|
|
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* GR-UI-015 (module extension): Every module with an i18n_path must ship
|
|
* matching default_de.json and default_en.json with identical keys and
|
|
* non-empty values.
|
|
*/
|
|
class ModuleI18nContractTest extends TestCase
|
|
{
|
|
use ProjectFileAssertionSupport;
|
|
|
|
/**
|
|
* @return list<array{string, string}>
|
|
*/
|
|
public static function moduleProvider(): array
|
|
{
|
|
$root = realpath(__DIR__ . '/../..');
|
|
if ($root === false) {
|
|
return [];
|
|
}
|
|
|
|
$modules = [];
|
|
foreach (glob($root . '/modules/*/module.php') as $manifestFile) {
|
|
$raw = (static function (string $path): mixed {
|
|
return require $path;
|
|
})($manifestFile);
|
|
if (!is_array($raw)) {
|
|
continue;
|
|
}
|
|
$i18nPath = $raw['i18n_path'] ?? null;
|
|
if (!is_string($i18nPath) || trim($i18nPath) === '') {
|
|
continue;
|
|
}
|
|
$moduleDir = dirname($manifestFile);
|
|
$moduleId = basename($moduleDir);
|
|
$resolvedPath = rtrim($moduleDir, '/') . '/' . ltrim(trim($i18nPath), '/');
|
|
$modules[$moduleId] = [$moduleId, $resolvedPath];
|
|
}
|
|
|
|
return array_values($modules);
|
|
}
|
|
|
|
#[DataProvider('moduleProvider')]
|
|
public function testModuleI18nFilesExistAndAreValidJson(string $moduleId, string $i18nPath): void
|
|
{
|
|
$this->assertDirectoryExists($i18nPath, "Module '{$moduleId}' declares i18n_path but directory does not exist.");
|
|
|
|
$deFile = $i18nPath . '/default_de.json';
|
|
$enFile = $i18nPath . '/default_en.json';
|
|
|
|
$this->assertFileExists($deFile, "Module '{$moduleId}' is missing default_de.json");
|
|
$this->assertFileExists($enFile, "Module '{$moduleId}' is missing default_en.json");
|
|
|
|
$deRaw = file_get_contents($deFile);
|
|
$enRaw = file_get_contents($enFile);
|
|
$this->assertNotFalse($deRaw);
|
|
$this->assertNotFalse($enRaw);
|
|
|
|
$this->assertIsArray(json_decode($deRaw, true), "Module '{$moduleId}' default_de.json is not valid JSON");
|
|
$this->assertIsArray(json_decode($enRaw, true), "Module '{$moduleId}' default_en.json is not valid JSON");
|
|
}
|
|
|
|
#[DataProvider('moduleProvider')]
|
|
public function testModuleI18nKeyParity(string $moduleId, string $i18nPath): void
|
|
{
|
|
$de = $this->loadModuleKeys($i18nPath . '/default_de.json');
|
|
$en = $this->loadModuleKeys($i18nPath . '/default_en.json');
|
|
|
|
$missingInEn = array_values(array_diff($de, $en));
|
|
$this->assertSame(
|
|
[],
|
|
$missingInEn,
|
|
"Module '{$moduleId}' default_de.json keys missing from default_en.json:\n" . implode("\n", $missingInEn)
|
|
);
|
|
|
|
$missingInDe = array_values(array_diff($en, $de));
|
|
$this->assertSame(
|
|
[],
|
|
$missingInDe,
|
|
"Module '{$moduleId}' default_en.json keys missing from default_de.json:\n" . implode("\n", $missingInDe)
|
|
);
|
|
}
|
|
|
|
#[DataProvider('moduleProvider')]
|
|
public function testModuleI18nValuesAreNonEmpty(string $moduleId, string $i18nPath): void
|
|
{
|
|
foreach (['default_de.json', 'default_en.json'] as $file) {
|
|
$path = $i18nPath . '/' . $file;
|
|
if (!is_file($path)) {
|
|
continue;
|
|
}
|
|
$data = json_decode(file_get_contents($path), true);
|
|
$this->assertIsArray($data);
|
|
|
|
$empty = array_keys(array_filter($data, static fn ($v): bool => trim((string) $v) === ''));
|
|
$this->assertSame(
|
|
[],
|
|
$empty,
|
|
"Module '{$moduleId}' {$file} has empty values:\n" . implode("\n", $empty)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
private function loadModuleKeys(string $path): array
|
|
{
|
|
if (!is_file($path)) {
|
|
return [];
|
|
}
|
|
$data = json_decode(file_get_contents($path), true);
|
|
if (!is_array($data)) {
|
|
return [];
|
|
}
|
|
|
|
$keys = array_keys($data);
|
|
sort($keys, SORT_STRING);
|
|
|
|
return $keys;
|
|
}
|
|
}
|