diff --git a/composer.json b/composer.json index 71913ca..535a325 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,8 @@ "nunomaduro/collision": "^7.0", "orchestra/testbench": "^8.0", "pestphp/pest": "^2.18", + "pestphp/pest-plugin-faker": "^2.0", + "pestphp/pest-plugin-laravel": "^2.2", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", diff --git a/database/factories/LanguageFactory.php b/database/factories/LanguageFactory.php index 85405a1..b5ecd68 100644 --- a/database/factories/LanguageFactory.php +++ b/database/factories/LanguageFactory.php @@ -12,9 +12,9 @@ class LanguageFactory extends Factory public function definition(): array { return [ + 'rtl' => $this->faker->boolean(), 'code' => $this->faker->randomElement(['en', 'nl', 'fr', 'de', 'es', 'it', 'pt', 'ru', 'ja', 'zh']), 'name' => $this->faker->randomElement(['English', 'Dutch', 'French', 'German', 'Spanish', 'Italian', 'Portuguese', 'Russian', 'Japanese', 'Chinese']), - 'rtl' => $this->faker->boolean(), ]; } } diff --git a/database/factories/TranslationFileFactory.php b/database/factories/TranslationFileFactory.php index 011442d..5309dcd 100644 --- a/database/factories/TranslationFileFactory.php +++ b/database/factories/TranslationFileFactory.php @@ -13,8 +13,15 @@ public function definition(): array { return [ 'name' => $this->faker->randomElement(['app', 'auth', 'pagination', 'passwords', 'validation']), - 'extension' => $this->faker->randomElement(['json', 'php']), + 'extension' => 'php', 'is_root' => false, ]; } + + public function json(): self + { + return $this->state([ + 'extension' => 'json', + ]); + } } diff --git a/database/migrations/add_is_root_to_translation_files_table.php b/database/migrations/add_is_root_to_translation_files_table.php index 7b32984..b5af6ab 100644 --- a/database/migrations/add_is_root_to_translation_files_table.php +++ b/database/migrations/add_is_root_to_translation_files_table.php @@ -6,24 +6,14 @@ return new class() extends Migration { - /** - * Run the migrations. - * - * @return void - */ - public function up() + public function up(): void { Schema::table('ltu_translation_files', function (Blueprint $table) { $table->boolean('is_root')->default(false)->after('extension'); }); } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() + public function down(): void { Schema::table('ltu_translation_files', function (Blueprint $table) { $table->dropColumn('is_root'); diff --git a/src/Actions/CopyPhrasesFromSourceAction.php b/src/Actions/CopyPhrasesFromSourceAction.php index b9bd34d..cc70f7e 100644 --- a/src/Actions/CopyPhrasesFromSourceAction.php +++ b/src/Actions/CopyPhrasesFromSourceAction.php @@ -3,6 +3,7 @@ namespace Outhebox\TranslationsUI\Actions; use Outhebox\TranslationsUI\Models\Translation; +use Outhebox\TranslationsUI\Models\TranslationFile; class CopyPhrasesFromSourceAction { @@ -11,14 +12,24 @@ public static function execute(Translation $translation): void $sourceTranslation = Translation::where('source', true)->first(); $sourceTranslation->phrases()->with('file')->get()->each(function ($sourcePhrase) use ($translation) { + $file = $sourcePhrase->file; + + if ($file->is_root) { + $file = TranslationFile::firstOrCreate([ + 'is_root' => true, + 'extension' => $file->extension, + 'name' => $translation->language->code, + ]); + } + $translation->phrases()->create([ 'value' => null, 'uuid' => str()->uuid(), 'key' => $sourcePhrase->key, - 'group' => $sourcePhrase->group, + 'group' => $file->name, 'phrase_id' => $sourcePhrase->id, 'parameters' => $sourcePhrase->parameters, - 'translation_file_id' => $sourcePhrase->file->id, + 'translation_file_id' => $file->id, ]); }); } diff --git a/src/Actions/SyncPhrasesAction.php b/src/Actions/SyncPhrasesAction.php index 993295e..36ac804 100644 --- a/src/Actions/SyncPhrasesAction.php +++ b/src/Actions/SyncPhrasesAction.php @@ -25,11 +25,16 @@ public static function execute(Translation $source, $key, $value, $locale, $file 'source' => config('translations.source_language') === $locale, ]); + $isRoot = $file === $locale.'.json' || $file === $locale.'.php'; + $translationFile = TranslationFile::firstOrCreate([ 'name' => pathinfo($file, PATHINFO_FILENAME), 'extension' => pathinfo($file, PATHINFO_EXTENSION), + 'is_root' => $isRoot, ]); + $key = config('translations.include_file_in_key') && ! $isRoot ? "{$translationFile->name}.{$key}" : $key; + $translation->phrases()->updateOrCreate([ 'key' => $key, 'group' => $translationFile->name, diff --git a/src/Http/Controllers/TranslationController.php b/src/Http/Controllers/TranslationController.php index 97adf16..8293137 100644 --- a/src/Http/Controllers/TranslationController.php +++ b/src/Http/Controllers/TranslationController.php @@ -72,8 +72,7 @@ public function store(Request $request): RedirectResponse 'languages' => 'required|array', ]); - $selectedLanguageIds = $request->input('languages'); - $languages = Language::whereIn('id', $selectedLanguageIds)->get(); + $languages = Language::whereIn('id', $request->input('languages'))->get(); foreach ($languages as $language) { CreateTranslationForLanguageAction::execute($language); diff --git a/src/TranslationsManager.php b/src/TranslationsManager.php index 1437362..3ddd67f 100644 --- a/src/TranslationsManager.php +++ b/src/TranslationsManager.php @@ -12,7 +12,7 @@ class TranslationsManager { public function __construct( - protected Filesystem $filesystem, + protected Filesystem $filesystem ) { } @@ -106,10 +106,10 @@ public function export(): void foreach ($phrasesTree as $locale => $groups) { foreach ($groups as $file => $phrases) { - $path = lang_path($file); + $path = lang_path("$locale/$file"); if (! $this->filesystem->isDirectory(dirname($path))) { - $this->filesystem->makeDirectory(dirname($path), 0o755, true); + $this->filesystem->makeDirectory(dirname($path), 0755, true); } if (! $this->filesystem->exists($path)) { diff --git a/src/TranslationsUIServiceProvider.php b/src/TranslationsUIServiceProvider.php index 4e0af77..24364eb 100644 --- a/src/TranslationsUIServiceProvider.php +++ b/src/TranslationsUIServiceProvider.php @@ -31,6 +31,7 @@ public function configurePackage(Package $package): void 'create_contributors_table', 'create_contributor_languages_table', 'create_invites_table', + 'add_is_root_to_translation_files_table', ]) ->hasCommands([ PublishCommand::class, diff --git a/tests/Helpers.php b/tests/Helpers.php index 6da096c..97a6e01 100644 --- a/tests/Helpers.php +++ b/tests/Helpers.php @@ -1,10 +1,10 @@ get(route('ltu.login')) - ->assertStatus(200); - - } - - /** @test */ - public function login_request_will_validate_email(): void - { - $response = $this->post(route('ltu.login.attempt'), [ - 'email' => 'not-an-email', - 'password' => 'password', - ])->assertRedirect(route('ltu.login')); - - $this->assertInstanceOf(ValidationException::class, $response->exception); - } - - /** @test */ - public function login_request_will_validate_password(): void - { - $response = $this->post(route('ltu.login.attempt'), [ - 'email' => $this->owner->email, - 'password' => 'what-is-my-password', - ])->assertSessionHasErrors(); - - $this->assertInstanceOf(ValidationException::class, $response->exception); - } - - /** @test */ - public function login_request_will_authenticate_user(): void - { - $this->withoutExceptionHandling(); - - $user = Contributor::factory([ - 'role' => RoleEnum::owner, - 'password' => Hash::make('password'), - ])->create(); - - $this->post(route('ltu.login.attempt'), [ - 'email' => $user->email, - 'password' => 'password', - ])->assertRedirect(route('ltu.translation.index')); - } - - /** @test */ - public function authenticated_users_can_access_dashboard(): void - { - $this->actingAs($this->owner, 'translations') - ->get(route('ltu.login')) - ->assertRedirect(route('ltu.translation.index')); - } - /** @test */ - public function authenticated_users_can_logout(): void - { - $this->actingAs($this->owner, 'translations') - ->get(route('ltu.logout')) - ->assertRedirect(route('ltu.login')); - } -} +it('login page can be rendered', function () { + $this->get(route('ltu.login')) + ->assertStatus(200); +}); + +it('login request will validate email', function () { + $response = $this->post(route('ltu.login.attempt'), [ + 'email' => 'not-an-email', + 'password' => 'password', + ])->assertRedirect(route('ltu.login')); + + expect($response->exception)->toBeInstanceOf(ValidationException::class); +}); + +it('login request will validate password', function () { + $response = $this->post(route('ltu.login.attempt'), [ + 'email' => $this->owner->email, + 'password' => 'what-is-my-password', + ])->assertSessionHasErrors(); + + expect($response->exception)->toBeInstanceOf(ValidationException::class); +}); + +it('login request will authenticate user', function () { + $this->withoutExceptionHandling(); + + $user = Contributor::factory([ + 'role' => RoleEnum::owner, + 'password' => Hash::make('password'), + ])->create(); + + $this->post(route('ltu.login.attempt'), [ + 'email' => $user->email, + 'password' => 'password', + ])->assertRedirect(route('ltu.translation.index')); +}); + +it('authenticated users can access dashboard', function () { + $this->actingAs($this->owner, 'translations') + ->get(route('ltu.login')) + ->assertRedirect(route('ltu.translation.index')); +}); + +it('authenticated users can logout', function () { + $this->actingAs($this->owner, 'translations') + ->get(route('ltu.logout')) + ->assertRedirect(route('ltu.login')); +}); diff --git a/tests/Http/Controllers/Auth/InvitationAcceptControllerTest.php b/tests/Http/Controllers/Auth/InvitationAcceptControllerTest.php index 0ae4684..26362a4 100644 --- a/tests/Http/Controllers/Auth/InvitationAcceptControllerTest.php +++ b/tests/Http/Controllers/Auth/InvitationAcceptControllerTest.php @@ -1,71 +1,55 @@ create(); - $this->get(route('ltu.invitation.accept', ['token' => $invite->token])) - ->assertStatus(200); - } +test('invitation accept page can be rendered', function () { + $invite = Invite::factory()->create(); - /** @test */ - public function invitation_accept_page_returns_404_if_token_is_invalid() - { - $this->get(route('ltu.invitation.accept', ['token' => 'invalid-token'])) - ->assertStatus(404); - } + $this->get(route('ltu.invitation.accept', ['token' => $invite->token])) + ->assertStatus(200); +}); - /** @test */ - public function invitation_accept_store_will_create_contributor_and_login() - { - $this->withoutExceptionHandling(); +test('invitation accept page returns 404 if token is invalid', function () { + $this->get(route('ltu.invitation.accept', ['token' => 'invalid-token'])) + ->assertStatus(404); +}); - $invite = Invite::factory()->create(); +test('invitation accept store will create contributor and login', function () { + $invite = Invite::factory()->create(); - $translator = Contributor::factory()->make([ - 'email' => $invite->email, - 'role' => RoleEnum::translator, - ]); + $translator = Contributor::factory()->make([ + 'email' => $invite->email, + 'role' => RoleEnum::translator, + ]); - $this->post(route('ltu.invitation.accept.store', ['token' => $invite->token]), [ - 'name' => $translator->name, - 'email' => $invite->email, - 'password' => 'password', - 'password_confirmation' => 'password', - ])->assertRedirect(route('ltu.translation.index')); + $this->post(route('ltu.invitation.accept.store', ['token' => $invite->token]), [ + 'name' => $translator->name, + 'email' => $invite->email, + 'password' => 'password', + 'password_confirmation' => 'password', + ])->assertRedirect(route('ltu.translation.index')); - $user = Contributor::where('email', $invite->email)->first(); + $user = Contributor::where('email', $invite->email)->first(); - $this->assertAuthenticatedAs($user, 'translations'); - } + $this->assertAuthenticatedAs($user, 'translations'); +}); - /** @test */ - public function invitation_accept_store_will_redirect_if_contributor_already_exists() - { - $invite = Invite::factory()->create(); +test('invitation accept store will redirect if contributor already exists', function () { + $invite = Invite::factory()->create(); - $contributor = Contributor::factory()->create([ - 'email' => $invite->email, - 'role' => RoleEnum::translator, - ]); + $contributor = Contributor::factory()->create([ + 'email' => $invite->email, + 'role' => RoleEnum::translator, + ]); - $this->post(route('ltu.invitation.accept.store', ['token' => $invite->token]), [ - 'name' => $contributor->name, - 'email' => $invite->email, - 'password' => 'password', - 'password_confirmation' => 'password', - ])->assertRedirect(route('ltu.login')); + $this->post(route('ltu.invitation.accept.store', ['token' => $invite->token]), [ + 'name' => $contributor->name, + 'email' => $invite->email, + 'password' => 'password', + 'password_confirmation' => 'password', + ])->assertRedirect(route('ltu.login')); - $this->assertGuest('translations'); - } -} + $this->assertGuest('translations'); +}); diff --git a/tests/Http/Controllers/Auth/NewPasswordControllerTest.php b/tests/Http/Controllers/Auth/NewPasswordControllerTest.php index e4e3233..6da4ecb 100644 --- a/tests/Http/Controllers/Auth/NewPasswordControllerTest.php +++ b/tests/Http/Controllers/Auth/NewPasswordControllerTest.php @@ -1,77 +1,56 @@ owner->id.'|'.Str::random()); - - cache(["password.reset.{$this->owner->id}" => $token], - now()->addMinutes(60) - ); - - $this->post(route('ltu.password.update', [ - 'token' => $token, - 'email' => $this->owner->email, - 'password' => 'password', - 'password_confirmation' => 'password', - ]))->assertRedirect(route('ltu.translation.index')); - - $this->assertEmpty(cache()->get("password.reset.{$this->owner->id}")); - } - - /** @test */ - public function new_password_request_will_validate_email(): void - { - $token = encrypt($this->owner->id.'|'.Str::random()); - - $response = $this->post(route('ltu.password.update'), [ - 'token' => $token, - 'email' => 'not-an-email', - 'password' => 'password', - 'password_confirmation' => 'password', - ]); - - $this->assertInstanceOf(ValidationException::class, $response->exception); - } - - /** @test */ - public function new_password_request_will_validate_unconfirmed_password(): void - { - $token = encrypt($this->owner->id.'|'.Str::random()); - - $response = $this->post(route('ltu.password.update'), [ - 'token' => $token, - 'email' => $this->owner->email, - 'password' => 'password', - 'password_confirmation' => 'secret', - ]); - - $this->assertInstanceOf(ValidationException::class, $response->exception); - } - /** @test */ - public function password_will_validate_bad_token(): void - { - $this->post(route('ltu.password.update'), [ - 'token' => Str::random(), - 'email' => $this->owner->email, - 'password' => 'password', - 'password_confirmation' => 'password', - ])->assertSessionHas('invalidResetToken'); - } -} +test('password can be reset', function () { + $token = encrypt($this->owner->id.'|'.Str::random()); + + cache(["password.reset.{$this->owner->id}" => $token], + now()->addMinutes(60) + ); + + $this->post(route('ltu.password.update', [ + 'token' => $token, + 'email' => $this->owner->email, + 'password' => 'password', + 'password_confirmation' => 'password', + ]))->assertRedirect(route('ltu.translation.index')); + + expect(cache()->get("password.reset.{$this->owner->id}"))->toBeEmpty(); +}); + +test('new password request will validate email', function () { + $token = encrypt($this->owner->id.'|'.Str::random()); + + $response = $this->post(route('ltu.password.update'), [ + 'token' => $token, + 'email' => 'not-an-email', + 'password' => 'password', + 'password_confirmation' => 'password', + ]); + + expect($response->exception)->toBeInstanceOf(ValidationException::class); +}); + +test('new password request will validate unconfirmed password', function () { + $token = encrypt($this->owner->id.'|'.Str::random()); + + $response = $this->post(route('ltu.password.update'), [ + 'token' => $token, + 'email' => $this->owner->email, + 'password' => 'password', + 'password_confirmation' => 'secret', + ]); + + expect($response->exception)->toBeInstanceOf(ValidationException::class); +}); + +test('password will validate bad token', function () { + $this->post(route('ltu.password.update'), [ + 'token' => Str::random(), + 'email' => $this->owner->email, + 'password' => 'password', + 'password_confirmation' => 'password', + ])->assertSessionHas('invalidResetToken'); +}); diff --git a/tests/Http/Controllers/Auth/PasswordResetLinkControllerTest.php b/tests/Http/Controllers/Auth/PasswordResetLinkControllerTest.php index fe3c8b0..33f7c3a 100644 --- a/tests/Http/Controllers/Auth/PasswordResetLinkControllerTest.php +++ b/tests/Http/Controllers/Auth/PasswordResetLinkControllerTest.php @@ -1,45 +1,33 @@ get(route('ltu.password.request')) - ->assertOk(); - } - - /** @test */ - public function forgot_password_link_request_will_validate_email(): void - { - $response = $this->post(route('ltu.password.email'), [ - 'email' => 'not-an-email', - ]); - - $this->assertInstanceOf(ValidationException::class, $response->exception); - } - - /** @test */ - public function the_password_reset_link_can_sent(): void - { - Mail::fake(); - - $this->post(route('ltu.password.email'), [ - 'email' => $this->owner->email, - ]) - ->assertRedirect(route('ltu.password.request')); - - Mail::assertSent(ResetPassword::class, function ($mail) { - $this->assertIsString($mail->token); - - return $mail->hasTo($this->owner->email); - }); - } -} + +test('forgot password link screen can be rendered', function () { + $this->get(route('ltu.password.request')) + ->assertOk(); +}); + +test('forgot password link request will validate email', function () { + $response = $this->post(route('ltu.password.email'), [ + 'email' => 'not-an-email', + ]); + + $this->assertInstanceOf(ValidationException::class, $response->exception); +}); + +test('the password reset link can sent', function () { + Mail::fake(); + + $this->post(route('ltu.password.email'), [ + 'email' => $this->owner->email, + ]) + ->assertRedirect(route('ltu.password.request')); + + Mail::assertSent(ResetPassword::class, function ($mail) { + $this->assertIsString($mail->token); + + return $mail->hasTo($this->owner->email); + }); +}); diff --git a/tests/Http/Controllers/ContributorControllerTest.php b/tests/Http/Controllers/ContributorControllerTest.php index 877d559..dca09dd 100644 --- a/tests/Http/Controllers/ContributorControllerTest.php +++ b/tests/Http/Controllers/ContributorControllerTest.php @@ -1,58 +1,41 @@ actingAs($this->owner, 'translations')->get(route('ltu.contributors.index')) - ->assertStatus(200); - } +it('it can render the contributor page', function () { + $this->actingAs($this->owner, 'translations')->get(route('ltu.contributors.index')) + ->assertStatus(200); +}); - /** @test */ - public function test_owner_can_invite_a_contributor() - { - $this->withoutExceptionHandling(); +test('owner can invite a contributor', function () { + Mail::fake(); - Mail::fake(); + $response = $this->actingAs($this->owner, 'translations')->post(route('ltu.contributors.invite.store'), [ + 'email' => 'email@example.com', + 'role' => RoleEnum::owner->value, + ]); - $response = $this->actingAs($this->owner, 'translations')->post(route('ltu.contributors.invite.store'), [ - 'email' => 'email@example.com', - 'role' => RoleEnum::owner->value, + $response->assertRedirect(route('ltu.contributors.index').'#invited') + ->assertSessionHas('notification', [ + 'type' => 'success', + 'body' => 'Invite sent successfully', ]); - $response->assertRedirect(route('ltu.contributors.index').'#invited') - ->assertSessionHas('notification', [ - 'type' => 'success', - 'body' => 'Invite sent successfully', - ]); - - $invite = Invite::where('email', 'email@example.com')->first(); - - $this->assertNotNull($invite); - - Mail::assertSent(InviteCreated::class, function ($mail) use ($invite) { - return $mail->hasTo($invite->email); - }); - } - - /** @test */ - public function test_owner_cannot_invite_a_contributor_with_invalid_email() - { - $this->actingAs($this->owner, 'translations')->post(route('ltu.contributors.invite.store'), [ - 'email' => 'invalid-email', - 'role' => RoleEnum::owner->value, - ])->assertSessionHasErrors('email'); - } -} + $invite = Invite::where('email', 'email@example.com')->first(); + + expect($invite)->not->toBeNull(); + + Mail::assertSent(InviteCreated::class, function ($mail) use ($invite) { + return $mail->hasTo($invite->email); + }); +}); + +test('owner cannot invite a contributor with invalid email', function () { + $this->actingAs($this->owner, 'translations')->post(route('ltu.contributors.invite.store'), [ + 'email' => 'invalid-email', + 'role' => RoleEnum::owner->value, + ])->assertSessionHasErrors('email'); +}); diff --git a/tests/Http/Controllers/PhraseControllerTest.php b/tests/Http/Controllers/PhraseControllerTest.php new file mode 100644 index 0000000..6a8a1b0 --- /dev/null +++ b/tests/Http/Controllers/PhraseControllerTest.php @@ -0,0 +1,53 @@ + true, + ])->create(); + + $SourcePhrase = Phrase::factory(5)->withParameters()->create([ + 'translation_id' => $sourceTranslation->id, + ]); + + $this->translation = Translation::factory()->create(); + + Phrase::factory()->create([ + 'translation_id' => $this->translation->id, + ]); + + $this->phrase = Phrase::factory()->create([ + 'translation_id' => $this->translation->id, + 'phrase_id' => $SourcePhrase->first()->id, + ]); +}); + +it('can render phrases page', function () { + $this->actingAs($this->owner, 'translations') + ->get(route('ltu.phrases.index', $this->translation)) + ->assertStatus(200); +}); + +it('can render phrases edit page', function () { + $this->actingAs($this->owner, 'translations') + ->get(route('ltu.phrases.edit', [ + 'phrase' => $this->phrase->uuid, + 'translation' => $this->translation->id, + ])) + ->assertStatus(200); +}); + +it('can update phrase', function () { + $this->actingAs($this->owner, 'translations') + ->post(route('ltu.phrases.update', [ + 'phrase' => $this->phrase->uuid, + 'translation' => $this->translation->id, + ]), [ + 'phrase' => fake()->sentence(), + ]) + ->assertStatus(302); +}); diff --git a/tests/Http/Controllers/ProfileControllerTest.php b/tests/Http/Controllers/ProfileControllerTest.php index b7260d1..36b6990 100644 --- a/tests/Http/Controllers/ProfileControllerTest.php +++ b/tests/Http/Controllers/ProfileControllerTest.php @@ -1,81 +1,61 @@ actingAs($this->translator, 'translations') - ->get(route('ltu.profile.edit')) - ->assertOk(); - } - - /** - * @test - * - * @throws JsonException - */ - public function profile_information_can_be_updated(): void - { - $this->actingAs($this->translator, 'translations') - ->from(route('ltu.profile.edit')) - ->patch(route('ltu.profile.update'), [ - 'name' => 'Test User', - 'email' => 'test@example.com', - ]) - ->assertSessionHasNoErrors() - ->assertRedirect(route('ltu.profile.edit')); - - $this->translator->refresh(); - - $this->assertSame('Test User', $this->translator->name); - $this->assertSame('test@example.com', $this->translator->email); - } - - /** - * @test - * - * @throws JsonException - */ - public function password_can_be_updated(): void - { - $this->withoutExceptionHandling(); - - $this->actingAs($this->translator, 'translations') - ->from(route('ltu.profile.edit')) - ->put(route('ltu.profile.password.update'), [ - 'current_password' => 'password', - 'password' => 'new-password', - 'password_confirmation' => 'new-password', - ]) - ->assertSessionHasNoErrors() - ->assertRedirect(route('ltu.profile.edit')); - - $this->translator->refresh(); - - $this->assertTrue(Hash::check('new-password', $this->translator->refresh()->password)); - } - - /** @test */ - public function correct_password_must_be_provided_to_update_password(): void - { - $response = $this - ->actingAs($this->translator, 'translations') - ->from(route('ltu.profile.edit')) - ->put(route('ltu.profile.password.update'), [ - 'current_password' => 'wrong-password', - 'password' => 'new-password', - 'password_confirmation' => 'new-password', - ]); - $response - ->assertSessionHasErrors('current_password') - ->assertRedirect(route('ltu.profile.edit')); - } -} +use function Pest\Faker\fake; + +it('can access the profile page', function () { + $this->actingAs($this->translator, 'translations') + ->get(route('ltu.profile.edit')) + ->assertOk(); +}); + +it('can update profile information', function () { + $name = fake()->name; + $email = fake()->safeEmail; + + $this->actingAs($this->translator, 'translations') + ->from(route('ltu.profile.edit')) + ->patch(route('ltu.profile.update'), [ + 'name' => $name, + 'email' => $email, + ]) + ->assertSessionHasNoErrors() + ->assertRedirect(route('ltu.profile.edit')); + + $this->translator->refresh(); + + expect($this->translator->name)->toBe($name) + ->and($this->translator->email)->toBe($email); +}); + +it('can update the password', function () { + $this->actingAs($this->translator, 'translations') + ->from(route('ltu.profile.edit')) + ->put(route('ltu.profile.password.update'), [ + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]) + ->assertSessionHasNoErrors() + ->assertRedirect(route('ltu.profile.edit')); + + $this->translator->refresh(); + + expect(Hash::check('new-password', $this->translator->refresh()->password))->toBeTrue(); +}); + +it('can update profile information with password', function () { + $response = $this + ->actingAs($this->translator, 'translations') + ->from(route('ltu.profile.edit')) + ->put(route('ltu.profile.password.update'), [ + 'current_password' => 'wrong-password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]); + + $response + ->assertSessionHasErrors('current_password') + ->assertRedirect(route('ltu.profile.edit')); +}); diff --git a/tests/Http/Controllers/SourcePhraseControllerTest.php b/tests/Http/Controllers/SourcePhraseControllerTest.php new file mode 100644 index 0000000..679eff4 --- /dev/null +++ b/tests/Http/Controllers/SourcePhraseControllerTest.php @@ -0,0 +1,78 @@ + true, + ])->create(); + + $this->source_phrase = Phrase::factory(5)->withParameters()->create([ + 'translation_id' => $sourceTranslation->id, + ]); + + $this->translation = Translation::factory()->create(); + + Phrase::factory()->create([ + 'translation_id' => $this->translation->id, + ]); + + $this->phrase = Phrase::factory()->create([ + 'translation_id' => $this->translation->id, + 'phrase_id' => $this->source_phrase->first()->id, + ]); +}); + +it('can render source phrases page', function () { + $this->actingAs($this->owner, 'translations') + ->get(route('ltu.source_translation')) + ->assertStatus(200); +}); + +it('can render source phrases edit page', function () { + $this->actingAs($this->owner, 'translations') + ->get(route('ltu.source_translation.edit', $this->source_phrase->first()->uuid)) + ->assertStatus(200); +}); + +it('can update source phrase', function () { + $this->actingAs($this->owner, 'translations') + ->post(route('ltu.source_translation.update', $this->source_phrase->first()->uuid), [ + 'note' => fake()->sentence, + 'phrase' => fake()->sentence, + 'file' => $this->source_phrase->first()->translation_file_id, + ])->assertRedirect(route('ltu.source_translation')); +}); + +it('can delete source phrase', function () { + $this->actingAs($this->owner, 'translations') + ->delete(route('ltu.source_translation.delete_phrase', $this->source_phrase->first()->uuid)) + ->assertRedirect(route('ltu.source_translation')); +}); + +it('can delete multiple source phrases', function () { + $this->actingAs($this->owner, 'translations') + ->post(route('ltu.source_translation.delete_phrases', [ + 'selected_ids' => [$this->source_phrase->pluck('id')->first()], + ]))->assertRedirect(route('ltu.source_translation')); +}); + +it('can add new source key', function () { + $file = TranslationFile::factory()->create(); + + $phrase = Phrase::factory()->make([ + 'translation_id' => $this->translation->id, + 'translation_file_id' => $file->id, + ]); + + $this->actingAs($this->owner, 'translations') + ->post(route('ltu.source_translation.store_source_key'), [ + 'file' => $file->id, + 'key' => $phrase->key, + 'content' => $phrase->value, + ])->assertRedirect(route('ltu.source_translation')); +}); diff --git a/tests/Http/Controllers/TranslationControllerTest.php b/tests/Http/Controllers/TranslationControllerTest.php new file mode 100644 index 0000000..7f62600 --- /dev/null +++ b/tests/Http/Controllers/TranslationControllerTest.php @@ -0,0 +1,51 @@ + true, + ])->create(); + + $this->source_phrase = Phrase::factory(5)->withParameters()->create([ + 'translation_id' => $sourceTranslation->id, + ]); + + $this->translation = Translation::factory()->create(); +}); + +it('can render translations page', function () { + $this->actingAs($this->owner, 'translations') + ->get(route('ltu.translation.index')) + ->assertStatus(200); +}); + +it('can store new translation', function () { + $this->actingAs($this->owner, 'translations') + ->post(route('ltu.translation.store'), [ + 'languages' => [Language::inRandomOrder()->first()->id], + ]) + ->assertRedirect(route('ltu.translation.index')); + + $this->assertCount(3, Translation::all()); +}); + +it('translation can be deleted', function () { + $this->actingAs($this->owner, 'translations') + ->delete(route('ltu.translation.delete', $this->translation->id)) + ->assertRedirect(route('ltu.translation.index')); + + $this->assertCount(1, Translation::all()); +}); + +it('multiple translations can be deleted', function () { + $this->actingAs($this->owner, 'translations') + ->post(route('ltu.translation.delete_multiple', [ + 'selected_ids' => [$this->translation->id], + ])) + ->assertRedirect(route('ltu.translation.index')); + + $this->assertCount(1, Translation::all()); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..e1acc05 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,5 @@ +in(__DIR__); diff --git a/tests/TranslationsManagerTest.php b/tests/TranslationsManagerTest.php index d3c143c..13ea9af 100644 --- a/tests/TranslationsManagerTest.php +++ b/tests/TranslationsManagerTest.php @@ -87,8 +87,6 @@ }); test('export creates a new translation file with the correct content', function () { - $this->markTestSkipped(); - $filesystem = new Filesystem(); createDirectoryIfNotExits(lang_path('en/auth.php')); @@ -110,6 +108,7 @@ $translationsManager->export(); $fileName = lang_path('en/'.$translation->phrases[0]->file->name.'.'.$translation->phrases[0]->file->extension); + $fileNameInDisk = File::allFiles(lang_path($translation->language->code))[0]->getPathname(); expect($fileName)->toBe($fileNameInDisk)