diff --git a/README.md b/README.md index 203c0ed..333d380 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ [![Github Workflow Status][badge_build]][link_build] [![License][badge_license]](https://laravel-lang.com/license.html) +## ToDo + +У астрономик взять принцип хранения данных, а у Спати - способ их использования. + +- https://github.com/Astrotomic/laravel-translatable +- https://github.com/spatie/laravel-translatable + ## Documentation See the [documentation](https://laravel-lang.com/packages-models.html) for detailed installation. diff --git a/composer.json b/composer.json index 542736d..f8bd69f 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "translations", "translate", "localization", + "locales", "l18n" ], "authors": [ @@ -29,8 +30,12 @@ }, "require": { "php": "^8.1", + "composer/class-map-generator": "^1.3", + "dragon-code/support": "^6.13", + "illuminate/database": "^10.0 || ^11.0", "illuminate/support": "^10.0 || ^11.0", - "laravel-lang/config": "^1.1" + "laravel-lang/config": "^1.4.2", + "laravel-lang/locales": "^2.8" }, "require-dev": { "orchestra/testbench": "^8.23 || ^9.1", @@ -62,6 +67,13 @@ "preferred-install": "dist", "sort-packages": true }, + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Models\\ServiceProvider" + ] + } + }, "scripts": { "test": "vendor/bin/pest --parallel" } diff --git a/database/migrations/2024_06_09_234306_create_translations_table.php b/database/migrations/2024_06_09_234306_create_translations_table.php new file mode 100644 index 0000000..abada68 --- /dev/null +++ b/database/migrations/2024_06_09_234306_create_translations_table.php @@ -0,0 +1,45 @@ +config(); + + Schema::connection($config->connection)->create($config->table, function (Blueprint $table) { + $table->id(); + + $table->string('model_type', 255); + $table->string('model_id', 255); + + $table->jsonb('content')->nullable(); + + $table->timestamps(); + $table->softDeletes(); + }); + + DB::statement( + "CREATE UNIQUE INDEX {$config->table}_model_type_model_id_unique ON {$config->table} (model_type, model_id) WHERE deleted_at IS NULL" + ); + } + + public function down(): void + { + $config = $this->config(); + + Schema::connection($config->connection)->dropIfExists($config->table); + } + + protected function config(): ModelsData + { + return Config::shared()->models; + } +}; diff --git a/phpunit.xml b/phpunit.xml index 2829ec8..7c7083b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,5 +20,8 @@ + + + diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/Casts/TranslationCast.php b/src/Casts/TranslationCast.php new file mode 100644 index 0000000..1f32f3b --- /dev/null +++ b/src/Casts/TranslationCast.php @@ -0,0 +1,32 @@ +toJson(Config::shared()->models->flags); + } + + return null; + } +} diff --git a/src/Concerns/HasSetUp.php b/src/Concerns/HasSetUp.php new file mode 100644 index 0000000..fcb67b2 --- /dev/null +++ b/src/Concerns/HasSetUp.php @@ -0,0 +1,18 @@ +models; + + $this->connection = $config->connection; + $this->table = $config->table; + } +} diff --git a/src/Concerns/HasStrings.php b/src/Concerns/HasStrings.php new file mode 100644 index 0000000..4374cab --- /dev/null +++ b/src/Concerns/HasStrings.php @@ -0,0 +1,17 @@ +cleanUp(); + $this->process(); + } + + protected function process(): void + { + foreach ($this->models() as $model) { + $this->components->task($model, fn () => $this->generate($model)); + } + } + + protected function generate(string $model): void + { + HelperGenerator::of($model)->generate(); + } + + protected function models(): array + { + return ClassMap::get(); + } + + protected function cleanUp(): void + { + Directory::ensureDelete( + Config::shared()->models->helpers + ); + } +} diff --git a/src/Data/ContentData.php b/src/Data/ContentData.php new file mode 100644 index 0000000..7a738a0 --- /dev/null +++ b/src/Data/ContentData.php @@ -0,0 +1,114 @@ +locale($locale) : $this->getDefault(); + + $value = $this->trim($value); + + if ($value instanceof ContentData) { + $this->locales[$column] = $value->getRaw($column); + + return; + } + + is_array($value) + ? $this->locales[$column] = $value + : $this->locales[$column][$locale] = $value; + } + + public function get(string $column, Locale|string|null $locale = null): float|int|string|null + { + if ($locale) { + return $this->locales[$column][$this->locale($locale)] ?? null; + } + + return $this->locales[$column][$this->getDefault()] + ?? $this->locales[$column][$this->getFallback()] + ?? null; + } + + public function has(string $column, Locale|string|null $locale = null): bool + { + if ($locale) { + return isset($this->locales[$column][$this->locale($locale)]); + } + + return isset($this->locales[$column][$this->getDefault()]) + || isset($this->locales[$column][$this->getFallback()]); + } + + public function forget(string $column, Locale|string|null $locale = null): void + { + if ($locale) { + unset($this->locales[$column][$this->locale($locale)]); + + return; + } + + unset($this->locales[$column]); + } + + public function getRaw(?string $path = null): mixed + { + return $path ? data_get($this->locales, $path) : $this->locales; + } + + public function toJson($options = 0): ?string + { + if ($items = $this->toArray()) { + return json_encode($items, $options); + } + + return null; + } + + public function toArray(): array + { + return collect($this->locales) + ->map(fn (array $locale) => array_filter($locale, fn (mixed $value) => ! blank($value))) + ->filter() + ->all(); + } + + protected function locale(Locale|string|null $locale): string + { + if (! Locales::isInstalled($locale)) { + throw new UnavailableLocaleException($locale); + } + + return Locales::get($locale)->code; + } + + protected function getDefault(): string + { + return Locales::getDefault()->code; + } + + protected function getFallback(): string + { + return Locales::getFallback()->code; + } +} diff --git a/src/Events/AllTranslationsHasBeenForgetEvent.php b/src/Events/AllTranslationsHasBeenForgetEvent.php new file mode 100644 index 0000000..8743a2f --- /dev/null +++ b/src/Events/AllTranslationsHasBeenForgetEvent.php @@ -0,0 +1,17 @@ +translatable()); + + parent::__construct( + sprintf( + 'Cannot translate attribute `%s` as it\'s not on of the translatable attributes: `%s`.', + $column, + $available + ) + ); + } +} diff --git a/src/Exceptions/UnavailableLocaleException.php b/src/Exceptions/UnavailableLocaleException.php new file mode 100644 index 0000000..80112ac --- /dev/null +++ b/src/Exceptions/UnavailableLocaleException.php @@ -0,0 +1,28 @@ +value ?? $locale, + $this->available() + ) + ); + } + + protected function available(): string + { + return Locales::installed()->pluck('locale.code')->filter()->implode(', '); + } +} diff --git a/src/HasTranslations.php b/src/HasTranslations.php new file mode 100644 index 0000000..0747e17 --- /dev/null +++ b/src/HasTranslations.php @@ -0,0 +1,163 @@ +translation?->setAttribute('model_id', $model->getKey()); + $model->translation?->save(); + + if (! $model->translation) { + $model->setRelation('translation', $model->translation()->make()); + } + }); + + static::deleting(function (Model $model) { + // @var \LaravelLang\Models\HasTranslations $model + return $model->translation?->delete() ?? $model->translation()->delete(); + }); + + if (method_exists(static::class, 'forceDeleted')) { + static::forceDeleted(function (Model $model) { + // @var \LaravelLang\Models\HasTranslations $model + return $model->translation?->forceDelete() ?? $model->translation()->forceDelete(); + }); + } + + if (method_exists(static::class, 'restored')) { + static::restored(function (Model $model) { + // @var \LaravelLang\Models\HasTranslations $model + $model->translation()->onlyTrashed()?->restore(); + }); + } + } + + public function translation(): MorphOne + { + return $this->morphOne(Translation::class, 'model'); + } + + public function setTranslation( + string $column, + array|ContentData|float|int|string|null $value, + Locale|string|null $locale = null + ): static { + $this->validateTranslationColumn($column, $locale, true); + + if (is_null($this->translation)) { + $this->setRelation('translation', $this->translation()->make()); + } + + TranslationHasBeenSetEvent::dispatch( + $this, + $column, + $locale?->value ?? $locale, + $this->getTranslation($column, $locale), + $value + ); + + $this->translation->content->set($column, $value, $locale); + + return $this; + } + + public function getTranslation(string $column, Locale|string|null $locale = null): float|int|string|null + { + $this->validateTranslationColumn($column, $locale); + + return $this->translation?->content?->get($column, $locale); + } + + public function hasTranslated(string $column, Locale|string|null $locale = null): bool + { + $this->validateTranslationColumn($column, $locale); + + return $this->translation->content?->has($column, $locale) ?? false; + } + + public function isTranslatable(string $column): bool + { + return in_array($column, $this->translatable(), true); + } + + public function forgetTranslation(string $column, Locale|string|null $locale = null): void + { + $this->validateTranslationColumn($column, $locale); + + $this->translation->content?->forget($column, $locale); + + TranslationHasBeenForgetEvent::dispatch($this, $column, $locale?->value ?? $locale); + } + + public function forgetAllTranslations(): void + { + $this->translation?->setAttribute('content', new ContentData([])); + + AllTranslationsHasBeenForgetEvent::dispatch($this); + } + + public function translatable(): array + { + return []; + } + + public function getAttribute($key): mixed + { + if ($this->isTranslatable($key)) { + return $this->getTranslation($key); + } + + return parent::getAttribute($key); + } + + public function newInstance($attributes = [], $exists = false): static + { + $basic = Arr::except($attributes, $this->translatable()); + $translatable = Arr::only($attributes, $this->translatable()); + + $model = parent::newInstance($basic, $exists); + + foreach ($translatable as $key => $value) { + $model->setTranslation($key, $value); + } + + return $model; + } + + protected function validateTranslationColumn( + string $column, + Locale|string|null $locale, + bool $withInstalled = false + ): void { + if (! $this->isTranslatable($column)) { + throw new AttributeIsNotTranslatableException($column, $this); + } + + if ($locale && ! $withInstalled && ! Locales::isInstalled($locale)) { + throw new UnavailableLocaleException($locale); + } + } +} diff --git a/src/Models/Translation.php b/src/Models/Translation.php new file mode 100644 index 0000000..0c67126 --- /dev/null +++ b/src/Models/Translation.php @@ -0,0 +1,43 @@ + 'string', + 'model_id' => 'string', + + 'content' => TranslationCast::class, + ]; + + public function parent(): MorphTo + { + return $this->morphTo(); + } + + protected function casts(): array + { + return $this->casts; + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php new file mode 100644 index 0000000..001fc49 --- /dev/null +++ b/src/ServiceProvider.php @@ -0,0 +1,33 @@ +app->runningInConsole() || $this->app->runningUnitTests()) { + $this->bootCommands(); + $this->bootMigrations(); + } + } + + protected function bootCommands(): void + { + $this->commands([ + ModelsHelperCommand::class, + ]); + } + + protected function bootMigrations(): void + { + $this->loadMigrationsFrom( + __DIR__ . '/../database/migrations' + ); + } +} diff --git a/src/Services/ClassMap.php b/src/Services/ClassMap.php new file mode 100644 index 0000000..459e67d --- /dev/null +++ b/src/Services/ClassMap.php @@ -0,0 +1,36 @@ +keys() + ->filter(static fn (string $class) => static::isTranslatable($class)) + ->all(); + } + + protected static function map(): array + { + return ClassMapGenerator::createMap(static::path()); + } + + protected static function path(): string + { + return Config::hidden()->models->directory; + } + + protected static function isTranslatable(string $class): bool + { + return Instance::of($class, HasTranslations::class); + } +} diff --git a/src/Services/HelperGenerator.php b/src/Services/HelperGenerator.php new file mode 100644 index 0000000..2550245 --- /dev/null +++ b/src/Services/HelperGenerator.php @@ -0,0 +1,95 @@ +store( + $this->make() + ); + } + + protected function make(): string + { + return Str::of($this->stub())->replaceFormat([ + 'namespace' => $this->getNamespace(), + 'model' => $this->getName(), + 'hash' => $this->getHash(), + 'properties' => $this->getProperties(), + ], '{{%s}}')->toString(); + } + + protected function getProperties(): string + { + return $this->getTranslatable($this->class) + ->map(fn (string $attribute) => sprintf($this->template, $attribute)) + ->implode(PHP_EOL); + } + + protected function getNamespace(): string + { + return IS::beforeLast($this->class, '\\'); + } + + protected function getName(): string + { + return class_basename($this->class); + } + + protected function getHash(): string + { + return md5($this->class); + } + + protected function getTranslatable(string $class): Collection + { + return collect($this->initializeModel($class)->translatable()); + } + + /** + * @return Model|\LaravelLang\Models\HasTranslations + */ + protected function initializeModel(string $class): Model + { + return new $class(); + } + + protected function stub(): string + { + return file_get_contents(__DIR__ . '/../../stubs/helper.stub'); + } + + protected function store(string $content): void + { + File::store($this->filename(), $content); + } + + protected function filename(): string + { + return Config::shared()->models->helpers . '/' . $this->filenamePrefix . $this->getHash() . '.php'; + } +} diff --git a/stubs/helper.stub b/stubs/helper.stub new file mode 100644 index 0000000..3a47a13 --- /dev/null +++ b/stubs/helper.stub @@ -0,0 +1,12 @@ +assertSuccessful(); -}); diff --git a/tests/Fixtures/Models/TestModel.php b/tests/Fixtures/Models/TestModel.php new file mode 100644 index 0000000..7bdd9b6 --- /dev/null +++ b/tests/Fixtures/Models/TestModel.php @@ -0,0 +1,35 @@ + $value]); +} + +function jsonEncodeRaw(array $value): string +{ + return json_encode($value, Config::shared()->models->flags); +} diff --git a/tests/Helpers/models.php b/tests/Helpers/models.php new file mode 100644 index 0000000..2ab493b --- /dev/null +++ b/tests/Helpers/models.php @@ -0,0 +1,60 @@ +word; + + $model = TestModel::create(compact('key')); + + if ($main || $fallback || $custom || $uninstalled) { + fakeTranslation($model, $main, $fallback, $custom); + } + + return $model; +} + +function fakeTranslation( + TestModel $model, + ?string $text = null, + ?string $fallback = null, + ?string $custom = null, + ?string $uninstalled = null +): void { + $data = []; + + if ($text) { + $data[LocaleValue::ColumnTitle][LocaleValue::LocaleMain] = $text; + } + + if ($fallback) { + $data[LocaleValue::ColumnTitle][LocaleValue::LocaleFallback] = $fallback; + } + + if ($custom) { + $data[LocaleValue::ColumnTitle][LocaleValue::LocaleCustom] = $custom; + } + + if ($uninstalled) { + $data[LocaleValue::ColumnTitle][LocaleValue::LocaleUninstalled] = $uninstalled; + } + + $model->translation->fill([ + 'content' => new ContentData($data), + ])->save(); +} + +function findFakeModel(): TestModel +{ + return TestModel::firstOrFail(); +} diff --git a/tests/Pest.php b/tests/Pest.php index 01e5067..138e799 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,7 +1,5 @@ in('Feature'); +use Illuminate\Foundation\Testing\RefreshDatabase; -// expect()->extend('toBeOne', function () { -// return $this->toBe(1); -// }); +uses(Tests\TestCase::class, RefreshDatabase::class)->in('Unit'); diff --git a/tests/TestCase.php b/tests/TestCase.php index 70a76eb..b5f4059 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,26 +2,41 @@ namespace Tests; +use Illuminate\Config\Repository; +use LaravelLang\Config\Enums\Name; use LaravelLang\Config\ServiceProvider as ConfigServiceProvider; use LaravelLang\Locales\ServiceProvider as LocalesServiceProvider; +use LaravelLang\Models\ServiceProvider as ModelsServiceProvider; use Orchestra\Testbench\TestCase as BaseTestCase; +use Tests\Concerns\Locales; use Tests\Constants\LocaleValue; abstract class TestCase extends BaseTestCase { + use Locales; + protected function getPackageProviders($app): array { return [ - LocalesServiceProvider::class, ConfigServiceProvider::class, + ModelsServiceProvider::class, + LocalesServiceProvider::class, ]; } protected function defineEnvironment($app): void { - /** @var \Illuminate\Config\Repository $config */ + /** @var Repository $config */ $config = $app['config']; $config->set('app.locale', LocaleValue::LocaleMain); + $config->set('app.fallback_locale', LocaleValue::LocaleFallback); + + $config->set(Name::Hidden() . '.models.directory', __DIR__ . '/Fixtures/Models'); + } + + protected function defineDatabaseMigrations(): void + { + $this->loadMigrationsFrom(__DIR__ . '/database/migrations'); } } diff --git a/tests/Unit/Console/ModelsTest.php b/tests/Unit/Console/ModelsTest.php new file mode 100644 index 0000000..2217d11 --- /dev/null +++ b/tests/Unit/Console/ModelsTest.php @@ -0,0 +1,35 @@ + Directory::ensureDelete( + Config::shared()->models->helpers +)); + +test('generation', function () { + $path = sprintf( + '%s/_ide_helper_models_%s.php', + Config::shared()->models->helpers, + md5(TestModel::class) + ); + + expect($path)->not->toBeReadableFile(); + + artisan(ModelsHelperCommand::class)->run(); + + expect($path)->toBeReadableFile(); + + expect(file_get_contents($path)) + ->toContain('Tests\Fixtures\Models') + ->toContain('TestModel') + ->toContain('@property string $title') + ->toContain('@property string $description') + ->not->toContain('@property string $key'); +}); diff --git a/tests/Unit/Events/AllTranslationsHasBeenForgetEventTest.php b/tests/Unit/Events/AllTranslationsHasBeenForgetEventTest.php new file mode 100644 index 0000000..084f807 --- /dev/null +++ b/tests/Unit/Events/AllTranslationsHasBeenForgetEventTest.php @@ -0,0 +1,58 @@ + fake()->word, + + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleFallback => 'qwerty 11', + ], + + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 20', + LocaleValue::LocaleFallback => 'qwerty 21', + ], + ]); + + Event::fake(AllTranslationsHasBeenForgetEvent::class); +}); + +test('all', function () { + $model = findFakeModel(); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe('qwerty 10'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + + $model->forgetAllTranslations(); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeFalse(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeNull(); + + expect($model->translation->content->getRaw())->toBeEmpty(); + + Event::assertDispatched(function (AllTranslationsHasBeenForgetEvent $event) use ($model) { + return $event->model->getKey() === $model->getKey(); + }); +}); diff --git a/tests/Unit/Events/TranslationHasBeenForgetEventTest.php b/tests/Unit/Events/TranslationHasBeenForgetEventTest.php new file mode 100644 index 0000000..e86ff86 --- /dev/null +++ b/tests/Unit/Events/TranslationHasBeenForgetEventTest.php @@ -0,0 +1,90 @@ + fake()->word, + + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleFallback => 'qwerty 11', + ], + + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 20', + LocaleValue::LocaleFallback => 'qwerty 21', + ], + ]); + + Event::fake(TranslationHasBeenForgetEvent::class); +}); + +test('column', function () { + $model = findFakeModel(); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe('qwerty 10'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + + $model->forgetTranslation(LocaleValue::ColumnTitle); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + + Event::assertDispatched(function (TranslationHasBeenForgetEvent $event) use ($model) { + return $event->model->getKey() === $model->getKey() + && $event->column === LocaleValue::ColumnTitle + && $event->locale === null; + }); +}); + +test('locale', function () { + $model = findFakeModel(); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe('qwerty 10'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + + $model->forgetTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + + Event::assertDispatched(function (TranslationHasBeenForgetEvent $event) use ($model) { + return $event->model->getKey() === $model->getKey() + && $event->column === LocaleValue::ColumnTitle + && $event->locale === LocaleValue::LocaleMain; + }); +}); diff --git a/tests/Unit/Events/TranslationHasBeenSetEventTest.php b/tests/Unit/Events/TranslationHasBeenSetEventTest.php new file mode 100644 index 0000000..5d13e77 --- /dev/null +++ b/tests/Unit/Events/TranslationHasBeenSetEventTest.php @@ -0,0 +1,121 @@ + Event::fake(TranslationHasBeenSetEvent::class) +); + +test('default locale', function () { + $oldText = fake()->paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(main: $oldText); + + expect($model->title)->toBeString()->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText); + + expect($model->title)->toBeString()->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($newText); + + Event::assertDispatched(function (TranslationHasBeenSetEvent $event) use ($model, $oldText, $newText) { + return $event->model->getKey() === $model->getKey() + && $event->column === LocaleValue::ColumnTitle + && $event->locale === null + && $event->oldValue === $oldText + && $event->newValue === $newText; + }); +}); + +test('main locale', function () { + $oldText = fake()->paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(main: $oldText); + + expect($model->title)->toBeString()->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText, LocaleValue::LocaleMain); + + expect($model->title)->toBeString()->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($newText); + + Event::assertDispatched(function (TranslationHasBeenSetEvent $event) use ($model, $oldText, $newText) { + return $event->model->getKey() === $model->getKey() + && $event->column === LocaleValue::ColumnTitle + && $event->locale === LocaleValue::LocaleMain + && $event->oldValue === $oldText + && $event->newValue === $newText; + }); +}); + +test('fallback locale', function () { + $oldText = fake()->paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(fallback: $oldText); + + expect($model->title)->toBeString()->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText, LocaleValue::LocaleFallback); + + expect($model->title)->toBeString()->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe($newText); + + Event::assertDispatched(function (TranslationHasBeenSetEvent $event) use ($model, $oldText, $newText) { + return $event->model->getKey() === $model->getKey() + && $event->column === LocaleValue::ColumnTitle + && $event->locale === LocaleValue::LocaleFallback + && $event->oldValue === $oldText + && $event->newValue === $newText; + }); +}); + +test('custom locale', function () { + $oldText = fake()->paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(custom: $oldText); + + expect($model->title)->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText, LocaleValue::LocaleCustom); + + expect($model->title)->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe($newText); + + Event::assertDispatched(function (TranslationHasBeenSetEvent $event) use ($model, $oldText, $newText) { + return $event->model->getKey() === $model->getKey() + && $event->column === LocaleValue::ColumnTitle + && $event->locale === LocaleValue::LocaleCustom + && $event->oldValue === $oldText + && $event->newValue === $newText; + }); +}); diff --git a/tests/Unit/Models/CreateTest.php b/tests/Unit/Models/CreateTest.php new file mode 100644 index 0000000..f5543f5 --- /dev/null +++ b/tests/Unit/Models/CreateTest.php @@ -0,0 +1,215 @@ + 'foo', + + LocaleValue::ColumnTitle => 'qwerty 10', + LocaleValue::ColumnDescription => 'qwerty 20', + ]); + + expect($model->key)->toBeString()->toBe('foo'); + expect($model->title)->toBeString()->toBe('qwerty 10'); + expect($model->description)->toBeString()->toBe('qwerty 20'); + + assertDatabaseHas(TestModel::class, [ + 'id' => $model->id, + 'key' => $model->key, + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + + 'content' => jsonEncodeRaw([ + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + ], + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 20', + ], + ]), + ]); +}); + +test('array', function () { + assertDatabaseEmpty(TestModel::class); + assertDatabaseEmpty(Translation::class); + + $model = TestModel::create([ + 'key' => 'foo', + + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleFallback => 'qwerty 11', + LocaleValue::LocaleCustom => 'qwerty 12', + ], + + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 20', + LocaleValue::LocaleFallback => 'qwerty 21', + LocaleValue::LocaleCustom => 'qwerty 22', + ], + ]); + + expect($model->key)->toBeString()->toBe('foo'); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe('qwerty 10'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe('qwerty 12'); + + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleCustom))->toBe('qwerty 22'); + + assertDatabaseHas(TestModel::class, [ + 'id' => $model->id, + 'key' => $model->key, + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + + 'content' => jsonEncodeRaw([ + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleFallback => 'qwerty 11', + LocaleValue::LocaleCustom => 'qwerty 12', + ], + + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 20', + LocaleValue::LocaleFallback => 'qwerty 21', + LocaleValue::LocaleCustom => 'qwerty 22', + ], + ]), + ]); +}); + +test('data object', function () { + assertDatabaseEmpty(TestModel::class); + assertDatabaseEmpty(Translation::class); + + $data1 = new ContentData([ + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleFallback => 'qwerty 11', + LocaleValue::LocaleCustom => 'qwerty 12', + ], + + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 20', + LocaleValue::LocaleFallback => 'qwerty 21', + LocaleValue::LocaleCustom => 'qwerty 22', + ], + ]); + + $data2 = new ContentData([ + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 30', + LocaleValue::LocaleFallback => 'qwerty 31', + LocaleValue::LocaleCustom => 'qwerty 32', + ], + + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 40', + LocaleValue::LocaleFallback => 'qwerty 41', + LocaleValue::LocaleCustom => 'qwerty 42', + ], + ]); + + $model = TestModel::create([ + 'key' => 'foo', + + LocaleValue::ColumnTitle => $data1, + LocaleValue::ColumnDescription => $data2, + ]); + + expect($model->key)->toBeString()->toBe('foo'); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe('qwerty 10'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe('qwerty 12'); + + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 40'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 41'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleCustom))->toBe('qwerty 42'); + + assertDatabaseHas(TestModel::class, [ + 'id' => $model->id, + 'key' => $model->key, + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + + 'content' => jsonEncodeRaw([ + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleFallback => 'qwerty 11', + LocaleValue::LocaleCustom => 'qwerty 12', + ], + + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 40', + LocaleValue::LocaleFallback => 'qwerty 41', + LocaleValue::LocaleCustom => 'qwerty 42', + ], + ]), + ]); +}); + +test('uninstalled store', function () { + assertDatabaseEmpty(TestModel::class); + assertDatabaseEmpty(Translation::class); + + $model = TestModel::create([ + 'key' => 'foo', + + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleUninstalled => 'qwerty 11', + ], + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + + 'content' => jsonEncodeRaw([ + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleUninstalled => 'qwerty 11', + ], + ]), + ]); +}); + +test('uninstalled reading', function () { + $model = TestModel::create([ + 'key' => 'foo', + + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleUninstalled => 'qwerty 11', + ], + ]); + + $model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleUninstalled); +})->throws(UnavailableLocaleException::class); diff --git a/tests/Unit/Models/DeleteTest.php b/tests/Unit/Models/DeleteTest.php new file mode 100644 index 0000000..c52e05e --- /dev/null +++ b/tests/Unit/Models/DeleteTest.php @@ -0,0 +1,104 @@ + $model->id, + 'deleted_at' => null, + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'deleted_at' => null, + ]); + + $model->delete(); + + assertDatabaseHas(TestModel::class, [ + 'id' => $model->id, + 'deleted_at' => now(), + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'deleted_at' => now(), + ]); +}); + +test('force delete', function () { + $model = fakeModel(main: 'foo'); + + assertDatabaseHas(TestModel::class, [ + 'id' => $model->id, + 'deleted_at' => null, + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'deleted_at' => null, + ]); + + $model->forceDelete(); + + assertDatabaseMissing(TestModel::class, [ + 'id' => $model->id, + ]); + + assertDatabaseMissing(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + ]); +}); + +test('restore', function () { + $model = fakeModel(main: 'foo'); + + assertDatabaseHas(TestModel::class, [ + 'id' => $model->id, + 'deleted_at' => null, + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'deleted_at' => null, + ]); + + $model->delete(); + + assertDatabaseHas(TestModel::class, [ + 'id' => $model->id, + 'deleted_at' => now(), + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'deleted_at' => now(), + ]); + + $model->restore(); + + assertDatabaseHas(TestModel::class, [ + 'id' => $model->id, + 'deleted_at' => null, + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'deleted_at' => null, + ]); +}); diff --git a/tests/Unit/Models/ForgetTest.php b/tests/Unit/Models/ForgetTest.php new file mode 100644 index 0000000..b36a632 --- /dev/null +++ b/tests/Unit/Models/ForgetTest.php @@ -0,0 +1,116 @@ + TestModel::create([ + 'key' => fake()->word, + + LocaleValue::ColumnTitle => [ + LocaleValue::LocaleMain => 'qwerty 10', + LocaleValue::LocaleFallback => 'qwerty 11', + ], + + LocaleValue::ColumnDescription => [ + LocaleValue::LocaleMain => 'qwerty 20', + LocaleValue::LocaleFallback => 'qwerty 21', + ], + ]) +); + +test('column', function () { + $model = findFakeModel(); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe('qwerty 10'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + + $model->forgetTranslation(LocaleValue::ColumnTitle); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); +}); + +test('locale', function () { + $model = findFakeModel(); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe('qwerty 10'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + + $model->forgetTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); +}); + +test('all', function () { + $model = findFakeModel(); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeTrue(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe('qwerty 10'); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe('qwerty 11'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBe('qwerty 20'); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBe('qwerty 21'); + + $model->forgetAllTranslations(); + + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeFalse(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnDescription, LocaleValue::LocaleFallback))->toBeNull(); + + expect($model->translation->content->getRaw())->toBeEmpty(); +}); + +test('non-translatable column', function () { + $model = fakeModel(); + + $model->forgetTranslation('foo'); +})->throws(AttributeIsNotTranslatableException::class); + +test('non-translatable locale', function () { + $model = fakeModel(); + + $model->forgetTranslation(LocaleValue::ColumnTitle, 'foo'); +})->throws(UnavailableLocaleException::class); diff --git a/tests/Unit/Models/GetTest.php b/tests/Unit/Models/GetTest.php new file mode 100644 index 0000000..780cd15 --- /dev/null +++ b/tests/Unit/Models/GetTest.php @@ -0,0 +1,102 @@ +paragraph; + + $model = fakeModel(main: $text); + + expect($model->title)->toBeString()->toBe($text); + + expect($model->translation->content->get(LocaleValue::ColumnTitle))->toBe($text); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($text); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeNull(); + + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($text); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($text); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeNull(); +}); + +test('fallback locale', function () { + $text = fake()->paragraph; + + $model = fakeModel(fallback: $text); + + expect($model->title)->toBeString()->toBe($text); + + expect($model->translation->content->get(LocaleValue::ColumnTitle))->toBe($text); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe($text); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeNull(); + + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($text); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe($text); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeNull(); +}); + +test('custom locale', function () { + $text = fake()->paragraph; + + $model = fakeModel(custom: $text); + + expect($model->title)->toBeNull(); + + expect($model->translation->content->get(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe($text); + + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe($text); +}); + +test('uninstalled', function () { + $model = fakeModel(uninstalled: fake()->paragraph); + + $model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleUninstalled); +})->throws(UnavailableLocaleException::class); + +test('without translations model', function () { + $model = fakeModel(); + + assertDatabaseEmpty(Translation::class); + + expect($model->title)->toBeNull(); + + expect($model->translation->content->get(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->translation->content->get(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeNull(); + + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeNull(); +}); + +test('non-translatable attribute', function () { + $key = fake()->word; + + $model = fakeModel($key); + + expect($model->key)->toBeString()->toBe($key); +}); + +test('not translatable attribute', function () { + $model = fakeModel(); + + $model->getTranslation('foo'); +})->throws(AttributeIsNotTranslatableException::class); diff --git a/tests/Unit/Models/HasTranslatedTest.php b/tests/Unit/Models/HasTranslatedTest.php new file mode 100644 index 0000000..d4ae2b9 --- /dev/null +++ b/tests/Unit/Models/HasTranslatedTest.php @@ -0,0 +1,36 @@ +paragraph + ); + + expect($model->hasTranslated(LocaleValue::ColumnTitle))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeFalse(); +}); + +test('fallback', function () { + $model = fakeModel( + fallback: fake()->paragraph + ); + + expect($model->hasTranslated(LocaleValue::ColumnTitle))->toBeTrue(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeTrue(); +}); + +test('custom', function () { + $model = fakeModel( + custom: fake()->paragraph + ); + + expect($model->hasTranslated(LocaleValue::ColumnTitle))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeFalse(); + expect($model->hasTranslated(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeTrue(); +}); diff --git a/tests/Unit/Models/IsTranslatableTest.php b/tests/Unit/Models/IsTranslatableTest.php new file mode 100644 index 0000000..6d37f2d --- /dev/null +++ b/tests/Unit/Models/IsTranslatableTest.php @@ -0,0 +1,15 @@ +isTranslatable(LocaleValue::ColumnTitle))->toBeTrue(); + expect($model->isTranslatable(LocaleValue::ColumnDescription))->toBeTrue(); + + expect($model->isTranslatable('key'))->toBeFalse(); + expect($model->isTranslatable('foo'))->toBeFalse(); +}); diff --git a/tests/Unit/Models/SetNonTranslatableTest.php b/tests/Unit/Models/SetNonTranslatableTest.php new file mode 100644 index 0000000..0524ea9 --- /dev/null +++ b/tests/Unit/Models/SetNonTranslatableTest.php @@ -0,0 +1,23 @@ +word; + + $model = fakeModel(); + + $model->key = $key; + $model->save(); + + expect($model->key)->toBeString()->toBe($key); +}); + +test('not translatable attribute', function () { + $model = fakeModel(); + + $model->setTranslation('foo', 'foo', LocaleValue::LocaleUninstalled); +})->throws(AttributeIsNotTranslatableException::class); diff --git a/tests/Unit/Models/SetWithSavingTest.php b/tests/Unit/Models/SetWithSavingTest.php new file mode 100644 index 0000000..2c0e5c5 --- /dev/null +++ b/tests/Unit/Models/SetWithSavingTest.php @@ -0,0 +1,155 @@ +paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(main: $oldText); + + expect($model->title)->toBeString()->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText); + $model->save(); + + expect($model->title)->toBeString()->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($newText); + + // Check database + assertDatabaseMissing(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleMain => $oldText]), + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleMain => $newText]), + ]); +}); + +test('fallback locale', function () { + $oldText = fake()->paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(fallback: $oldText); + + expect($model->title)->toBeString()->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText, LocaleValue::LocaleFallback); + $model->save(); + + expect($model->title)->toBeString()->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe($newText); + + // Check database + assertDatabaseMissing(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleFallback => $oldText]), + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleFallback => $newText]), + ]); +}); + +test('custom locale', function () { + $oldText = fake()->paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(custom: $oldText); + + expect($model->title)->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText, LocaleValue::LocaleCustom); + $model->save(); + + expect($model->title)->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe($newText); + + // Check database + assertDatabaseMissing(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleCustom => $oldText]), + ]); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleCustom => $newText]), + ]); +}); + +test('empties', function () { + $model = fakeModel(); + + $model->setTranslation(LocaleValue::ColumnTitle, null, LocaleValue::LocaleMain); + $model->setTranslation(LocaleValue::ColumnTitle, '', LocaleValue::LocaleFallback); + $model->setTranslation(LocaleValue::ColumnTitle, ' ', LocaleValue::LocaleCustom); + + $model->save(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeNull(); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => null, + ]); +}); + +test('numeric', function () { + $model = fakeModel(); + + $model->setTranslation(LocaleValue::ColumnTitle, 0, LocaleValue::LocaleMain); + $model->setTranslation(LocaleValue::ColumnTitle, 0.01, LocaleValue::LocaleFallback); + + $model->save(); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe(0); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe(0.01); + + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + + 'content' => jsonEncode([ + LocaleValue::LocaleMain => 0, + LocaleValue::LocaleFallback => 0.01, + ]), + ]); +}); + +test('unknown locale', function () { + $model = fakeModel(); + + $model->setTranslation(LocaleValue::ColumnTitle, 'foo', LocaleValue::LocaleUninstalled); +})->throws(UnavailableLocaleException::class); diff --git a/tests/Unit/Models/SetWithoutSavingTest.php b/tests/Unit/Models/SetWithoutSavingTest.php new file mode 100644 index 0000000..8a9657c --- /dev/null +++ b/tests/Unit/Models/SetWithoutSavingTest.php @@ -0,0 +1,130 @@ +paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(main: $oldText); + + expect($model->title)->toBeString()->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText); + + expect($model->title)->toBeString()->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe($newText); + + // Check database + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleMain => $oldText]), + ]); + + assertDatabaseMissing(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleMain => $newText]), + ]); +}); + +test('fallback locale', function () { + $oldText = fake()->paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(fallback: $oldText); + + expect($model->title)->toBeString()->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($oldText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText, LocaleValue::LocaleFallback); + + expect($model->title)->toBeString()->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBe($newText); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe($newText); + + // Check database + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleFallback => $oldText]), + ]); + + assertDatabaseMissing(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleFallback => $newText]), + ]); +}); + +test('custom locale', function () { + $oldText = fake()->paragraph; + $newText = fake()->paragraph; + + $model = fakeModel(custom: $oldText); + + expect($model->title)->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe($oldText); + + // Change that + $model->setTranslation(LocaleValue::ColumnTitle, $newText, LocaleValue::LocaleCustom); + + expect($model->title)->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBe($newText); + + // Check database + assertDatabaseHas(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleCustom => $oldText]), + ]); + + assertDatabaseMissing(Translation::class, [ + 'model_type' => TestModel::class, + 'model_id' => $model->id, + 'content' => jsonEncode([LocaleValue::LocaleCustom => $newText]), + ]); +}); + +test('empties', function () { + $model = fakeModel(); + + $model->setTranslation(LocaleValue::ColumnTitle, null, LocaleValue::LocaleMain); + $model->setTranslation(LocaleValue::ColumnTitle, '', LocaleValue::LocaleFallback); + $model->setTranslation(LocaleValue::ColumnTitle, ' ', LocaleValue::LocaleCustom); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBeNull(); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleCustom))->toBeNull(); + + assertDatabaseEmpty(Translation::class); +}); + +test('numeric', function () { + $model = fakeModel(); + + $model->setTranslation(LocaleValue::ColumnTitle, 0, LocaleValue::LocaleMain); + $model->setTranslation(LocaleValue::ColumnTitle, 0.01, LocaleValue::LocaleFallback); + + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleMain))->toBe(0); + expect($model->getTranslation(LocaleValue::ColumnTitle, LocaleValue::LocaleFallback))->toBe(0.01); + + assertDatabaseEmpty(Translation::class); +}); diff --git a/tests/database/migrations/2024_06_11_031722_create_test_table.php b/tests/database/migrations/2024_06_11_031722_create_test_table.php new file mode 100644 index 0000000..5d97bff --- /dev/null +++ b/tests/database/migrations/2024_06_11_031722_create_test_table.php @@ -0,0 +1,21 @@ +id(); + + $table->string('key'); + + $table->timestamps(); + $table->softDeletes(); + }); + } +};