mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Changes to Lang (fixes #33) with tests
This commit is contained in:
parent
9491d082ed
commit
6ec13266fa
9 changed files with 228 additions and 61 deletions
3
autoload.php
Normal file
3
autoload.php
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
require_once __DIR__.DIRECTORY_SEPARATOR."bootstrap.php";
|
||||
$data = new RuntimeData(new Conf());
|
|
@ -9,5 +9,3 @@ if(!defined(NS_BASE."INSTALL")) define(NS_BASE."INSTALL", false);
|
|||
|
||||
require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
|
||||
ignore_user_abort(true);
|
||||
|
||||
$data = new RuntimeData(new Conf());
|
|
@ -19,9 +19,9 @@ class TestConf extends \PHPUnit\Framework\TestCase {
|
|||
'confEmpty' => '',
|
||||
'confUnreadable' => '',
|
||||
]);
|
||||
self::$path = self::$vfs->url();
|
||||
self::$path = self::$vfs->url()."/";
|
||||
// set up a file without read access
|
||||
chmod(self::$path."/confUnreadable", 0000);
|
||||
chmod(self::$path."confUnreadable", 0000);
|
||||
}
|
||||
|
||||
static function tearDownAfterClass() {
|
||||
|
@ -48,9 +48,9 @@ class TestConf extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
function testImportFile() {
|
||||
$conf = new Conf();
|
||||
$conf->importFile(self::$path."/confGood");
|
||||
$conf->importFile(self::$path."confGood");
|
||||
$this->assertEquals("xx", $conf->lang);
|
||||
$conf = new Conf(self::$path."/confGood");
|
||||
$conf = new Conf(self::$path."confGood");
|
||||
$this->assertEquals("xx", $conf->lang);
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ class TestConf extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
function testImportFileMissing() {
|
||||
$this->assertException("fileMissing", "Conf");
|
||||
$conf = new Conf(self::$path."/confMissing");
|
||||
$conf = new Conf(self::$path."confMissing");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +67,7 @@ class TestConf extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
function testImportFileEmpty() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
$conf = new Conf(self::$path."/confEmpty");
|
||||
$conf = new Conf(self::$path."confEmpty");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,7 +75,7 @@ class TestConf extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
function testImportFileUnreadable() {
|
||||
$this->assertException("fileUnreadable", "Conf");
|
||||
$conf = new Conf(self::$path."/confUnreadable");
|
||||
$conf = new Conf(self::$path."confUnreadable");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,7 +83,7 @@ class TestConf extends \PHPUnit\Framework\TestCase {
|
|||
*/
|
||||
function testImportFileNotAnArray() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
$conf = new Conf(self::$path."/confNotArray");
|
||||
$conf = new Conf(self::$path."confNotArray");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +92,7 @@ class TestConf extends \PHPUnit\Framework\TestCase {
|
|||
function testImportFileNotPHP() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
// this should not print the output of the non-PHP file
|
||||
$conf = new Conf(self::$path."/confNotPHP");
|
||||
$conf = new Conf(self::$path."confNotPHP");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,6 +101,6 @@ class TestConf extends \PHPUnit\Framework\TestCase {
|
|||
function testImportFileCorrupt() {
|
||||
$this->assertException("fileCorrupt", "Conf");
|
||||
// this should not print the output of the non-PHP file
|
||||
$conf = new Conf(self::$path."/confCorrupt");
|
||||
$conf = new Conf(self::$path."confCorrupt");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,16 @@ class TestLang extends \PHPUnit\Framework\TestCase {
|
|||
static $vfs;
|
||||
static $path;
|
||||
static $files;
|
||||
static $defaultPath;
|
||||
|
||||
static function setUpBeforeClass() {
|
||||
// this is required to keep from having exceptions in Lang::msg() in turn calling Lang::msg() and looping
|
||||
Lang\Exception::$test = true;
|
||||
// test files
|
||||
self::$files = [
|
||||
'en.php' => '<?php return ["Test.presentText" => "and the Philosopher\'s Stone"];',
|
||||
'en-ca.php' => '<?php return [];',
|
||||
'en-us.php' => '<?php return ["Test.presentText" => "and the Sorcerer\'s Stone"];',
|
||||
'en_ca.php' => '<?php return ["Test.presentText" => "{0} and {1}"];',
|
||||
'en_us.php' => '<?php return ["Test.presentText" => "and the Sorcerer\'s Stone"];',
|
||||
'fr.php' => '<?php return ["Test.presentText" => "à l\'école des sorciers"];',
|
||||
'ja.php' => '<?php return ["Test.absentText" => "賢者の石"];',
|
||||
'de.php' => '<?php return ["Test.presentText" => "und der Stein der Weisen"];',
|
||||
|
@ -24,7 +27,7 @@ class TestLang extends \PHPUnit\Framework\TestCase {
|
|||
'it.php' => '<?php return 0;',
|
||||
'zh.php' => '<?php return 0',
|
||||
'ko.php' => 'DEAD BEEF',
|
||||
'fr-ca.php' => '',
|
||||
'fr_ca.php' => '',
|
||||
// unreadable file
|
||||
'ru.php' => '',
|
||||
];
|
||||
|
@ -32,16 +35,65 @@ class TestLang extends \PHPUnit\Framework\TestCase {
|
|||
self::$path = self::$vfs->url();
|
||||
// set up a file without read access
|
||||
chmod(self::$path."/ru.php", 0000);
|
||||
// make the Lang class use the vfs files
|
||||
self::$defaultPath = Lang::$path;
|
||||
Lang::$path = self::$path."/";
|
||||
}
|
||||
|
||||
static function tearDownAfterClass() {
|
||||
Lang\Exception::$test = false;
|
||||
Lang::$path = self::$defaultPath;
|
||||
self::$path = null;
|
||||
self::$vfs = null;
|
||||
self::$files = null;
|
||||
Lang::set(Lang::DEFAULT, true);
|
||||
}
|
||||
|
||||
function testList() {
|
||||
$this->assertEquals(sizeof(self::$files), sizeof(Lang::list("en", "vfs://langtest/")));
|
||||
$this->assertCount(sizeof(self::$files), Lang::list("en"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testList
|
||||
*/
|
||||
function testSet() {
|
||||
$this->assertEquals("en", Lang::set("en"));
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca"));
|
||||
$this->assertEquals("de", Lang::set("de_ch"));
|
||||
$this->assertEquals("en", Lang::set("en_gb_hixie"));
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca_jking"));
|
||||
$this->assertEquals("en", Lang::set("es"));
|
||||
$this->assertEquals("", Lang::set(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testSet
|
||||
*/
|
||||
function testLoadInternalStrings() {
|
||||
$this->assertEquals("", Lang::set("", true));
|
||||
$this->assertCount(sizeof(Lang::REQUIRED), Lang::dump());
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testLoadInternalStrings
|
||||
*/
|
||||
function testLoadDefaultStrings() {
|
||||
$this->assertEquals(Lang::DEFAULT, Lang::set(Lang::DEFAULT, true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayHasKey('Exception.JKingWeb/NewsSync/Exception.uncoded', $str);
|
||||
$this->assertArrayHasKey('Test.presentText', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testLoadDefaultStrings
|
||||
*/
|
||||
function testLoadMultipleFiles() {
|
||||
Lang::set(Lang::DEFAULT, true);
|
||||
$this->assertEquals("ja", Lang::set("ja", true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayHasKey('Exception.JKingWeb/NewsSync/Exception.uncoded', $str);
|
||||
$this->assertArrayHasKey('Test.presentText', $str);
|
||||
$this->assertArrayHasKey('Test.absentText', $str);
|
||||
}
|
||||
|
||||
}
|
|
@ -2,10 +2,7 @@
|
|||
declare(strict_types=1);
|
||||
namespace JKingWeb\NewsSync;
|
||||
|
||||
const BASE = __DIR__.DIRECTORY_SEPARATOR."..".DIRECTORY_SEPARATOR;
|
||||
const NS_BASE = __NAMESPACE__."\\";
|
||||
|
||||
require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
|
||||
require_once __DIR__.DIRECTORY_SEPARATOR."..".DIRECTORY_SEPARATOR."bootstrap.php";
|
||||
|
||||
trait TestingHelpers {
|
||||
function assertException(string $msg, string $prefix = "", string $type = "Exception") {
|
||||
|
@ -20,5 +17,3 @@ trait TestingHelpers {
|
|||
$this->expectExceptionCode($code);
|
||||
}
|
||||
}
|
||||
|
||||
ignore_user_abort(true);
|
|
@ -9,11 +9,12 @@
|
|||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTestSize="true">
|
||||
|
||||
<testsuite name="Base">
|
||||
<testsuite name="Localization and exceptions">
|
||||
<file>TestLang.php</file>
|
||||
<file>TestConf.php</file>
|
||||
<file>TestLangComplex.php</file>
|
||||
</testsuite>
|
||||
|
||||
|
||||
|
||||
<testsuite name="Configuration loading and saving">
|
||||
<file>TestConf.php</file>
|
||||
</testsuite>
|
||||
</phpunit>
|
106
tests/testLangComplex.php
Normal file
106
tests/testLangComplex.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\NewsSync;
|
||||
use \org\bovigo\vfs\vfsStream;
|
||||
|
||||
|
||||
class TestLangComplex extends \PHPUnit\Framework\TestCase {
|
||||
use TestingHelpers;
|
||||
|
||||
static $vfs;
|
||||
static $path;
|
||||
static $files;
|
||||
static $defaultPath;
|
||||
|
||||
static function setUpBeforeClass() {
|
||||
// this is required to keep from having exceptions in Lang::msg() in turn calling Lang::msg() and looping
|
||||
Lang\Exception::$test = true;
|
||||
// test files
|
||||
self::$files = [
|
||||
'en.php' => '<?php return ["Test.presentText" => "and the Philosopher\'s Stone"];',
|
||||
'en_ca.php' => '<?php return ["Test.presentText" => "{0} and {1}"];',
|
||||
'en_us.php' => '<?php return ["Test.presentText" => "and the Sorcerer\'s Stone"];',
|
||||
'fr.php' => '<?php return ["Test.presentText" => "à l\'école des sorciers"];',
|
||||
'ja.php' => '<?php return ["Test.absentText" => "賢者の石"];',
|
||||
'de.php' => '<?php return ["Test.presentText" => "und der Stein der Weisen"];',
|
||||
// corrupt files
|
||||
'it.php' => '<?php return 0;',
|
||||
'zh.php' => '<?php return 0',
|
||||
'ko.php' => 'DEAD BEEF',
|
||||
'fr_ca.php' => '',
|
||||
// unreadable file
|
||||
'ru.php' => '',
|
||||
];
|
||||
self::$vfs = vfsStream::setup("langtest", 0777, self::$files);
|
||||
self::$path = self::$vfs->url();
|
||||
// set up a file without read access
|
||||
chmod(self::$path."/ru.php", 0000);
|
||||
// make the Lang class use the vfs files
|
||||
self::$defaultPath = Lang::$path;
|
||||
Lang::$path = self::$path."/";
|
||||
}
|
||||
|
||||
static function tearDownAfterClass() {
|
||||
Lang\Exception::$test = false;
|
||||
Lang::$path = self::$defaultPath;
|
||||
self::$path = null;
|
||||
self::$vfs = null;
|
||||
self::$files = null;
|
||||
Lang::set(Lang::DEFAULT, true);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
Lang::set(Lang::DEFAULT, true);
|
||||
}
|
||||
|
||||
function testLoadLazy() {
|
||||
Lang::set("ja");
|
||||
$this->assertArrayNotHasKey('Test.absentText', Lang::dump());
|
||||
}
|
||||
|
||||
function testLoadCascade() {
|
||||
Lang::set("ja", true);
|
||||
$this->assertEquals("de", Lang::set("de", true));
|
||||
$str = Lang::dump();
|
||||
$this->assertArrayNotHasKey('Test.absentText', $str);
|
||||
$this->assertEquals('und der Stein der Weisen', $str['Test.presentText']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testLoadCascade
|
||||
*/
|
||||
function testLoadSubtag() {
|
||||
$this->assertEquals("en_ca", Lang::set("en_ca", true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testLoadSubtag
|
||||
*/
|
||||
function testMessage() {
|
||||
Lang::set("de", true);
|
||||
$this->assertEquals('und der Stein der Weisen', Lang::msg('Test.presentText'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testMessage
|
||||
*/
|
||||
function testMessageNumMSingle() {
|
||||
Lang::set("en_ca", true);
|
||||
$this->assertEquals('Default language file "en" missing', Lang::msg('Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing', Lang::DEFAULT));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testMessage
|
||||
*/
|
||||
function testMessageNumMulti() {
|
||||
Lang::set("en_ca", true);
|
||||
$this->assertEquals('Happy Rotter and the Philosopher\'s Stone', Lang::msg('Test.presentText', ['Happy Rotter', 'the Philosopher\'s Stone']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testMessage
|
||||
*/
|
||||
function testMessageNamed() {
|
||||
$this->assertEquals('Message string "Test.absentText" missing from all loaded language files (en)', Lang::msg('Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing', ['msgID' => 'Test.absentText', 'fileList' => 'en']));
|
||||
}
|
||||
}
|
50
vendor/JKingWeb/NewsSync/Lang.php
vendored
50
vendor/JKingWeb/NewsSync/Lang.php
vendored
|
@ -4,7 +4,6 @@ namespace JKingWeb\NewsSync;
|
|||
use \Webmozart\Glob\Glob;
|
||||
|
||||
class Lang {
|
||||
const PATH = BASE."locale".DIRECTORY_SEPARATOR;
|
||||
const DEFAULT = "en";
|
||||
const REQUIRED = [
|
||||
'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in Exception.php',
|
||||
|
@ -16,6 +15,7 @@ class Lang {
|
|||
'Exception.JKingWeb/NewsSync/Lang/Exception.stringInvalid' => 'Message string "{msgID}" is not a valid ICU message string (language files loaded: {fileList})',
|
||||
];
|
||||
|
||||
static public $path = BASE."locale".DIRECTORY_SEPARATOR;
|
||||
static protected $requirementsMet = false;
|
||||
static protected $synched = false;
|
||||
static protected $wanted = self::DEFAULT;
|
||||
|
@ -25,13 +25,16 @@ class Lang {
|
|||
|
||||
protected function __construct() {}
|
||||
|
||||
static public function set(string $locale = "", bool $immediate = false): string {
|
||||
static public function set(string $locale, bool $immediate = false): string {
|
||||
if(!self::$requirementsMet) self::checkRequirements();
|
||||
if($locale=="") $locale = self::DEFAULT;
|
||||
if($locale==self::$wanted) return $locale;
|
||||
if($locale != "") {
|
||||
$list = self::listFiles();
|
||||
if(!in_array(self::DEFAULT, $list)) throw new Lang\Exception("defaultFileMissing", self::DEFAULT);
|
||||
self::$wanted = self::match($locale, $list);
|
||||
} else {
|
||||
self::$wanted = "";
|
||||
}
|
||||
self::$synched = false;
|
||||
if($immediate) self::load();
|
||||
return self::$wanted;
|
||||
|
@ -41,12 +44,15 @@ class Lang {
|
|||
return (self::$locale=="") ? self::DEFAULT : self::$locale;
|
||||
}
|
||||
|
||||
static public function dump(): array {
|
||||
return self::$strings;
|
||||
}
|
||||
|
||||
static public function msg(string $msgID, $vars = null): string {
|
||||
// if we're trying to load the system default language and it fails, we have a chicken and egg problem, so we catch the exception and load no language file instead
|
||||
if(!self::$synched) try {self::load();} catch(Lang\Exception $e) {
|
||||
if(self::$wanted==self::DEFAULT) {
|
||||
self::set();
|
||||
self::load();
|
||||
self::set("", true);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
|
@ -64,9 +70,9 @@ class Lang {
|
|||
return $msg;
|
||||
}
|
||||
|
||||
static public function list(string $locale = "", string $path = self::PATH): array {
|
||||
static public function list(string $locale = ""): array {
|
||||
$out = [];
|
||||
$files = self::listFiles($path);
|
||||
$files = self::listFiles();
|
||||
foreach($files as $tag) {
|
||||
$out[$tag] = \Locale::getDisplayName($tag, ($locale=="") ? $tag : $locale);
|
||||
}
|
||||
|
@ -85,10 +91,12 @@ class Lang {
|
|||
return true;
|
||||
}
|
||||
|
||||
static protected function listFiles(string $path = self::PATH): array {
|
||||
$out = Glob::glob($path."*.php");
|
||||
static protected function listFiles(): array {
|
||||
$out = glob(self::$path."*.php");
|
||||
if(empty($out)) $out = Glob::glob(self::$path."*.php");
|
||||
$out = array_map(function($file) {
|
||||
$file = substr(str_replace(DIRECTORY_SEPARATOR, "/", $file),strrpos($file,"/")+1);
|
||||
$file = str_replace(DIRECTORY_SEPARATOR, "/", $file);
|
||||
$file = substr($file, strrpos($file, "/")+1);
|
||||
return strtolower(substr($file,0,strrpos($file,".")));
|
||||
},$out);
|
||||
natsort($out);
|
||||
|
@ -96,21 +104,19 @@ class Lang {
|
|||
}
|
||||
|
||||
static protected function load(): bool {
|
||||
self::$synched = true;
|
||||
if(!self::$requirementsMet) self::checkRequirements();
|
||||
// if we've yet to request a locale, just load the fallback strings and return
|
||||
// if we've requested no locale (""), just load the fallback strings and return
|
||||
if(self::$wanted=="") {
|
||||
self::$strings = self::REQUIRED;
|
||||
self::$locale = self::$wanted;
|
||||
self::$synched = true;
|
||||
return true;
|
||||
}
|
||||
// decompose the requested locale from specific to general, building a list of files to load
|
||||
$tags = \Locale::parseLocale(self::$wanted);
|
||||
$files = [];
|
||||
$loaded = [];
|
||||
$strings = [];
|
||||
while(sizeof($tags) > 0) {
|
||||
$files[] = \Locale::composeLocale($tags);
|
||||
$files[] = strtolower(\Locale::composeLocale($tags));
|
||||
$tag = array_pop($tags);
|
||||
}
|
||||
// include the default locale as the base if the most general locale requested is not the default
|
||||
|
@ -124,15 +130,21 @@ class Lang {
|
|||
$files[] = $file;
|
||||
}
|
||||
// if we need to load all files, start with the fallback strings
|
||||
if($files==$loaded) $strings[] = self::REQUIRED;
|
||||
$strings = [];
|
||||
if($files==$loaded) {
|
||||
$strings[] = self::REQUIRED;
|
||||
} else {
|
||||
// otherwise start with the strings we already have if we're going from e.g. "fr" to "fr_ca"
|
||||
$strings[] = self::$strings;
|
||||
}
|
||||
// read files in reverse order
|
||||
$files = array_reverse($files);
|
||||
foreach($files as $file) {
|
||||
if(!file_exists(self::PATH."$file.php")) throw new Lang\Exception("fileMissing", $file);
|
||||
if(!is_readable(self::PATH."$file.php")) throw new Lang\Exception("fileUnreadable", $file);
|
||||
if(!file_exists(self::$path."$file.php")) throw new Lang\Exception("fileMissing", $file);
|
||||
if(!is_readable(self::$path."$file.php")) throw new Lang\Exception("fileUnreadable", $file);
|
||||
try {
|
||||
ob_start();
|
||||
$arr = (include self::PATH."$file.php");
|
||||
$arr = (include self::$path."$file.php");
|
||||
} catch(\Throwable $e) {
|
||||
$arr = null;
|
||||
} finally {
|
||||
|
|
2
vendor/JKingWeb/NewsSync/Lang/Exception.php
vendored
2
vendor/JKingWeb/NewsSync/Lang/Exception.php
vendored
|
@ -18,7 +18,7 @@ class Exception extends \JKingWeb\NewsSync\Exception {
|
|||
$code = self::CODES[$codeID];
|
||||
$msg = "Exception.".str_replace("\\","/",__CLASS__).".$msgID";
|
||||
}
|
||||
\Exception::construct($msg, $code, $e);
|
||||
\Exception::__construct($msg, $code, $e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue