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();
+ });
+ }
+};