jaroslavtyc/drd-plus-codes

View on GitHub
tests/Codes/AllTranslatableCodesTest.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php declare(strict_types=1);

namespace DrdPlus\Tests\Codes;

use DrdPlus\Codes\Armaments\ShieldCode;
use DrdPlus\Codes\Armaments\WeaponCategoryCode;
use DrdPlus\Codes\Body\OrdinaryWoundOriginCode;
use DrdPlus\Codes\Body\SeriousWoundOriginCode;
use DrdPlus\Codes\Environment\LandingSurfaceCode;
use DrdPlus\Codes\Environment\MaterialCode;
use DrdPlus\Codes\GenderCode;
use DrdPlus\Codes\ItemHoldingCode;
use DrdPlus\Codes\Partials\TranslatableCode;
use DrdPlus\Codes\RaceCode;
use DrdPlus\Codes\Skills\SkillCode;
use DrdPlus\Codes\Skills\SkillTypeCode;
use DrdPlus\Codes\SubRaceCode;
use DrdPlus\Codes\Theurgist\AffectionPeriodCode;
use DrdPlus\Codes\Theurgist\DemonBodyCode;
use DrdPlus\Codes\Theurgist\DemonCode;
use DrdPlus\Codes\Theurgist\DemonKindCode;
use DrdPlus\Codes\Theurgist\DemonMutableParameterCode;
use DrdPlus\Codes\Theurgist\DemonTraitCode;
use DrdPlus\Codes\Theurgist\FormCode;
use DrdPlus\Codes\Theurgist\FormulaCode;
use DrdPlus\Codes\Theurgist\FormulaMutableParameterCode;
use DrdPlus\Codes\Theurgist\ModifierCode;
use DrdPlus\Codes\Theurgist\ModifierMutableParameterCode;
use DrdPlus\Codes\Theurgist\ProfileCode;
use DrdPlus\Codes\Theurgist\SpellTraitCode;
use DrdPlus\Codes\Wizard\SpellCode;
use DrdPlus\Tests\Codes\Partials\TranslatableCodeTest;
use Granam\TestWithMockery\TestWithMockery;

class AllTranslatableCodesTest extends TestWithMockery
{

    use GetCodeClassesTrait;

    /**
     * @test
     * @throws \ReflectionException
     */
    public function I_can_get_its_english_translation()
    {
        foreach ($this->getTranslatableCodeClasses() as $codeClass) {
            $testClass = $this->getTestClass($codeClass);
            self::assertTrue(
                is_a($testClass, TranslatableCodeTest::class, true),
                "Test class $testClass should be a descendant of " . TranslatableCodeTest::class
            );
            $hasSinglesOnly = $this->hasSinglesOnly($codeClass);
            $hasMultiplesOnly = $this->hasMultiplesOnly($codeClass);
            foreach ($codeClass::getPossibleValues() as $value) {
                /** @var TranslatableCode $sut */
                $sut = $codeClass::getIt($value);
                self::assertSame($this->codeToEnglish($value), $sut->translateTo('en'));
                self::assertSame($sut->translateTo('en'), $sut->translateTo('en', 1));
                if ($hasSinglesOnly || $hasMultiplesOnly || in_array($value, $this->getValuesSameInEnglishForAnyNumbers(), true)) {
                    self::assertSame($sut->translateTo('en', 1), $sut->translateTo('en', 2));
                } else {
                    self::assertNotSame(
                        $one = $sut->translateTo('en', 1),
                        $two = $sut->translateTo('en', 2),
                        "Expected different translation in english from $codeClass for numbers 1 and 2: $one, $two"
                    );
                }
                self::assertSame($sut->translateTo('en', 2), $sut->translateTo('en', 3));
                self::assertSame($sut->translateTo('en', 2), $sut->translateTo('en', 999));
            }
        }
    }

    protected function getValuesSameInEnglishForAnyNumbers(): array
    {
        return [
            'senses',
            'unarmed',
        ];
    }

    protected function hasSinglesOnly(string $codeClass): bool
    {
        foreach (
            [
                SkillTypeCode::class, SkillCode::class, ShieldCode::class, ItemHoldingCode::class,
                LandingSurfaceCode::class, MaterialCode::class, SeriousWoundOriginCode::class,
                OrdinaryWoundOriginCode::class, RaceCode::class, SubRaceCode::class, GenderCode::class,
                SpellCode::class, FormulaMutableParameterCode::class, FormCode::class, DemonCode::class,
                DemonMutableParameterCode::class, AffectionPeriodCode::class, DemonTraitCode::class,
                DemonBodyCode::class, FormulaCode::class, ModifierMutableParameterCode::class, ProfileCode::class,
                ModifierCode::class, SpellTraitCode::class, DemonKindCode::class,
            ] as $singleOnlyClass
        ) {
            if (is_a($codeClass, $singleOnlyClass, true)) {
                return true;
            }
        }

        return false;
    }

    protected function hasMultiplesOnly(string $codeClass): bool
    {
        foreach ([WeaponCategoryCode::class] as $multipleOnlyClass) {
            if (is_a($codeClass, $multipleOnlyClass, true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return array|TranslatableCode[]
     * @throws \ReflectionException
     */
    private function getTranslatableCodeClasses(): array
    {
        $translatableCodeClasses = [];
        foreach ($this->getCodeClasses() as $codeClass) {
            if (!is_a($codeClass, TranslatableCode::class, true)) {
                continue;
            }
            $translatableCodeClasses[] = $codeClass;
        }

        return $translatableCodeClasses;
    }

    private function getTestClass(string $codeClass): string
    {
        $testClass = str_replace('DrdPlus\\', 'DrdPlus\Tests\\', $codeClass) . 'Test';
        self::assertTrue(class_exists($testClass), "Estimated test class $testClass does not exists");

        return $testClass;
    }

    /**
     * @param string $code
     * @return string
     */
    protected function codeToEnglish(string $code): string
    {
        return preg_replace(
            ['~_dm_~', '~_~', '~run silently~', '~venus$~', '~mars$~'],
            ['_DM_', ' ', 'run silently!', '♀', '♂'],
            $code
        );
    }

    /**
     * @test
     * @throws \ReflectionException
     */
    public function I_can_get_its_czech_translation()
    {
        foreach ($this->getTranslatableCodeClasses() as $codeClass) {
            $hasSinglesOnly = $this->hasSinglesOnly($codeClass);
            $hasMultiplesOnly = $this->hasMultiplesOnly($codeClass);
            foreach ($codeClass::getPossibleValues() as $value) {
                /** @var TranslatableCode $sut */
                $sut = $codeClass::getIt($value);
                $oneInEnglish = $this->codeToEnglish($value);
                $oneInCzech = $sut->translateTo('cs');
                self::assertStringNotContainsString('_', $oneInCzech, 'Underscore is really weird in a translation');
                self::assertSame($oneInCzech, $sut->translateTo('cs', 1));
                if (in_array($oneInEnglish, $this->getValuesSameInCzechAndEnglish(), true)) {
                    self::assertSame($oneInEnglish, $oneInCzech);
                } else {
                    self::assertNotSame(
                        $oneInEnglish,
                        $oneInCzech,
                        "Expected translation of '{$value}' to be different in czech than in english in code {$codeClass}, got '{$oneInCzech}'"
                    );
                }
                $twoInCzech = $sut->translateTo('cs', 2);
                if ($hasSinglesOnly || $hasMultiplesOnly || in_array($oneInCzech, $this->getValuesSameInCzechForOneAndFew(), true)) {
                    self::assertSame(
                        $oneInCzech,
                        $twoInCzech,
                        "Expected same translation in czech from $codeClass for numbers 1 and 2: $oneInCzech, $twoInCzech"
                    );
                } else {
                    self::assertNotSame(
                        $oneInCzech,
                        $twoInCzech,
                        "Expected different translation in czech from $codeClass for numbers 1 and 2: $oneInCzech, $twoInCzech"
                    );
                }
                self::assertSame($twoInCzech, $threeInCzech = $sut->translateTo('cs', 3));
                self::assertSame($threeInCzech, $fourInCzech = $sut->translateTo('cs', 4));
                $fiveInCzech = $sut->translateTo('cs', 5);
                if ($hasSinglesOnly || $hasMultiplesOnly || in_array($fourInCzech, $this->getValuesSameInCzechForFewAndMany(), true)) {
                    self::assertSame($fourInCzech, $fiveInCzech);
                } else {
                    self::assertNotSame(
                        $fourInCzech,
                        $fiveInCzech,
                        "Expected different translation in czech from $codeClass for numbers 4 and 5: $fourInCzech, $fiveInCzech"
                    );
                }
                self::assertSame($fiveInCzech, $sut->translateTo('cs', 6));
                self::assertSame($fiveInCzech, $sut->translateTo('cs', 999));
            }
        }
    }

    /**
     * @return array|string[]
     */
    protected function getValuesSameInCzechAndEnglish(): array
    {
        return [
            'charisma', 'pony', 'elf', 'kroll', 'skurut', 'goblin',
            'nekrakosa', 'genius loci', 'berserk', 'delirium', // wizard
            'golem', 'receptor ♀', 'receptor ♂', 'receptor', 'amulet' // theurgist
        ];
    }

    /**
     * @return array|string[]
     */
    protected function getValuesSameInCzechForOneAndFew(): array
    {
        return [
            'vůle',
            'inteligence',
            'smysly',
            'maximální naložení',
            'infravize',
            'šavle',
            'kopí',
            'vidle',
            'minikuše',
            'hvězdice',
            'beze zbraně',
            'kuše',
            'pony',
            'stání',
        ];
    }

    /**
     * @return array|string[]
     */
    protected function getValuesSameInCzechForFewAndMany(): array
    {
        return [
            'smysly',
            'kopí',
            'beze zbraně',
            'kněží',
            'pony',
            'stání',
            'beze zbrojí',
            'bez helem',
        ];
    }

    /**
     * @test
     * @throws \ReflectionException
     */
    public function I_get_warning_for_unknown_locale()
    {
        foreach ($this->getTranslatableCodeClasses() as $codeClass) {
            foreach ($codeClass::getPossibleValues() as $value) {
                /** @var TranslatableCode $sut */
                $sut = $codeClass::getIt($value);
                $inEnglish = $this->codeToEnglish($value);
                $previousErrorReporting = error_reporting(-1 ^ E_USER_WARNING);
                error_clear_last();
                self::assertSame($inEnglish, $sut->translateTo('demonic'));
                $lastError = error_get_last();
                error_reporting($previousErrorReporting);
                error_clear_last();
                self::assertNotEmpty($lastError);
                self::assertSame(E_USER_WARNING, $lastError['type']);
                self::assertStringContainsString($value, $lastError['message']);
                self::assertStringContainsString('demonic', $lastError['message']);
            }
        }
    }
}