From 6c491be4e98b2eda2cdaf3aa49c9ed15d9498447 Mon Sep 17 00:00:00 2001 From: Yury Pikhtarev Date: Wed, 2 Jul 2025 03:10:04 +0400 Subject: [PATCH] refactor(emoji): rename variables for clarity in EmojiAlias and EmojiCategory controllers Updated variable names in the EmojiAliasController and EmojiCategoryController to improve code readability. Changed instances of `$emojiAlias` and `$emojiCategory` to `$alias` and `$category`, respectively, in the show, update, and destroy methods. Additionally, updated the UpdateEmojiAliasRequest to reflect the new variable naming convention. This enhances consistency and clarity across the codebase. --- .../Controllers/Api/EmojiAliasController.php | 18 +- .../Api/EmojiCategoryController.php | 16 +- .../Emoji/UpdateEmojiAliasRequest.php | 2 +- tests/Feature/Api/EmojiAliasTest.php | 347 ++++++++++++++++++ tests/Feature/Api/EmojiCategoryTest.php | 222 +++++++++++ tests/Feature/Api/EmojiTest.php | 333 +++++++++++++++++ 6 files changed, 920 insertions(+), 18 deletions(-) create mode 100644 tests/Feature/Api/EmojiAliasTest.php create mode 100644 tests/Feature/Api/EmojiCategoryTest.php create mode 100644 tests/Feature/Api/EmojiTest.php diff --git a/app/Http/Controllers/Api/EmojiAliasController.php b/app/Http/Controllers/Api/EmojiAliasController.php index dafd90e4f..a0b04796d 100644 --- a/app/Http/Controllers/Api/EmojiAliasController.php +++ b/app/Http/Controllers/Api/EmojiAliasController.php @@ -53,37 +53,37 @@ class EmojiAliasController extends Controller /** * Display the specified resource. */ - public function show(EmojiAlias $emojiAlias, Request $request) + public function show(EmojiAlias $alias, Request $request) { - $emojiAlias->load(['emoji' => function ($query) { + $alias->load(['emoji' => function ($query) { $query->with('category'); }]); - return new EmojiAliasResource($emojiAlias); + return new EmojiAliasResource($alias); } /** * Update the specified resource in storage. */ - public function update(UpdateEmojiAliasRequest $request, EmojiAlias $emojiAlias) + public function update(UpdateEmojiAliasRequest $request, EmojiAlias $alias) { - $emojiAlias->update($request->validated()); + $alias->update($request->validated()); if ($request->get('with_emoji')) { - $emojiAlias->load(['emoji' => function ($query) { + $alias->load(['emoji' => function ($query) { $query->with('category'); }]); } - return new EmojiAliasResource($emojiAlias); + return new EmojiAliasResource($alias); } /** * Remove the specified resource from storage. */ - public function destroy(EmojiAlias $emojiAlias) + public function destroy(EmojiAlias $alias) { - $emojiAlias->delete(); + $alias->delete(); return response()->json(null, 204); } diff --git a/app/Http/Controllers/Api/EmojiCategoryController.php b/app/Http/Controllers/Api/EmojiCategoryController.php index 836a06752..a78bdc301 100644 --- a/app/Http/Controllers/Api/EmojiCategoryController.php +++ b/app/Http/Controllers/Api/EmojiCategoryController.php @@ -44,9 +44,9 @@ class EmojiCategoryController extends Controller /** * Display the specified resource. */ - public function show(EmojiCategory $emojiCategory, Request $request) + public function show(EmojiCategory $category, Request $request) { - $emojiCategory->load([ + $category->load([ 'emojis' => function ($query) use ($request) { $query->orderBy('display_order'); if ($request->get('with_aliases')) { @@ -55,25 +55,25 @@ class EmojiCategoryController extends Controller }, ]); - return new EmojiCategoryResource($emojiCategory); + return new EmojiCategoryResource($category); } /** * Update the specified resource in storage. */ - public function update(UpdateEmojiCategoryRequest $request, EmojiCategory $emojiCategory) + public function update(UpdateEmojiCategoryRequest $request, EmojiCategory $category) { - $emojiCategory->update($request->validated()); + $category->update($request->validated()); - return new EmojiCategoryResource($emojiCategory); + return new EmojiCategoryResource($category); } /** * Remove the specified resource from storage. */ - public function destroy(EmojiCategory $emojiCategory) + public function destroy(EmojiCategory $category) { - $emojiCategory->delete(); + $category->delete(); return response()->json(null, 204); } diff --git a/app/Http/Requests/Emoji/UpdateEmojiAliasRequest.php b/app/Http/Requests/Emoji/UpdateEmojiAliasRequest.php index 9eab55a31..13549aa82 100644 --- a/app/Http/Requests/Emoji/UpdateEmojiAliasRequest.php +++ b/app/Http/Requests/Emoji/UpdateEmojiAliasRequest.php @@ -22,7 +22,7 @@ class UpdateEmojiAliasRequest extends FormRequest */ public function rules(): array { - $aliasId = $this->route('emoji_alias')->id; + $aliasId = $this->route('alias')->id; return [ 'emoji_id' => 'sometimes|exists:emojis,id', diff --git a/tests/Feature/Api/EmojiAliasTest.php b/tests/Feature/Api/EmojiAliasTest.php new file mode 100644 index 000000000..24c6f7a8d --- /dev/null +++ b/tests/Feature/Api/EmojiAliasTest.php @@ -0,0 +1,347 @@ +create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + EmojiAlias::factory()->count(3)->create(['emoji_id' => $emoji->id]); + + $response = $this->getJson('/api/emoji/aliases'); + + $response->assertOk() + ->assertJsonStructure([ + 'data' => [ + '*' => [ + 'id', + 'alias', + 'created_at', + 'updated_at', + ], + ], + 'links', + 'meta', + ]) + ->assertJsonCount(3, 'data'); + }); + + test('can filter aliases by emoji', function () { + $category = EmojiCategory::factory()->create(); + $emoji1 = Emoji::factory()->create(['emoji_category_id' => $category->id]); + $emoji2 = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + EmojiAlias::factory()->count(2)->create(['emoji_id' => $emoji1->id]); + EmojiAlias::factory()->count(3)->create(['emoji_id' => $emoji2->id]); + + $response = $this->getJson("/api/emoji/aliases?emoji_id={$emoji1->id}"); + + $response->assertOk() + ->assertJsonCount(2, 'data'); + }); + + test('can search aliases', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + EmojiAlias::factory()->create([ + 'emoji_id' => $emoji->id, + 'alias' => ':happy:', + ]); + EmojiAlias::factory()->create([ + 'emoji_id' => $emoji->id, + 'alias' => ':sad:', + ]); + + $response = $this->getJson('/api/emoji/aliases?search=happy'); + + $response->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.alias', ':happy:'); + }); + + test('can include emoji and category with aliases', function () { + $category = EmojiCategory::factory()->create(['title' => 'Test Category']); + $emoji = Emoji::factory()->create([ + 'emoji_category_id' => $category->id, + 'title' => 'Test Emoji', + ]); + EmojiAlias::factory()->create(['emoji_id' => $emoji->id]); + + $response = $this->getJson('/api/emoji/aliases?with_emoji=1'); + + $response->assertOk() + ->assertJsonPath('data.0.emoji.title', 'Test Emoji') + ->assertJsonPath('data.0.emoji.category.title', 'Test Category'); + }); + + test('can get specific alias', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + $alias = EmojiAlias::factory()->create([ + 'emoji_id' => $emoji->id, + 'alias' => ':test_alias:', + ]); + + $response = $this->getJson("/api/emoji/aliases/{$alias->id}"); + + $response->assertOk() + ->assertJsonPath('data.alias', ':test_alias:') + ->assertJsonStructure([ + 'data' => [ + 'id', + 'alias', + 'emoji', + 'created_at', + 'updated_at', + ], + ]); + }); + + test('returns 404 for non-existent alias', function () { + $response = $this->getJson('/api/emoji/aliases/999'); + + $response->assertNotFound(); + }); + + test('can create alias', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + $data = [ + 'emoji_id' => $emoji->id, + 'alias' => ':new_alias:', + ]; + + $response = $this->postJson('/api/emoji/aliases', $data); + + $response->assertCreated() + ->assertJsonPath('data.alias', ':new_alias:') + ->assertJsonPath('data.emoji', null); // Not loaded by default + + $this->assertDatabaseHas('emoji_aliases', [ + 'emoji_id' => $emoji->id, + 'alias' => ':new_alias:', + ]); + }); + + test('validates required fields when creating alias', function () { + $response = $this->postJson('/api/emoji/aliases', []); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['emoji_id', 'alias']); + }); + + test('validates alias format', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + $response = $this->postJson('/api/emoji/aliases', [ + 'emoji_id' => $emoji->id, + 'alias' => 'invalid-format', + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['alias']); + }); + + test('validates unique alias', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + EmojiAlias::factory()->create([ + 'emoji_id' => $emoji->id, + 'alias' => ':existing:', + ]); + + $response = $this->postJson('/api/emoji/aliases', [ + 'emoji_id' => $emoji->id, + 'alias' => ':existing:', + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['alias']); + }); + + test('validates emoji exists', function () { + $response = $this->postJson('/api/emoji/aliases', [ + 'emoji_id' => 999, + 'alias' => ':valid_alias:', + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['emoji_id']); + }); + + test('prevents alias from conflicting with emoji shortcode', function () { + $category = EmojiCategory::factory()->create(); + $emoji1 = Emoji::factory()->create([ + 'emoji_category_id' => $category->id, + 'emoji_shortcode' => ':existing_emoji:', + ]); + $emoji2 = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + $response = $this->postJson('/api/emoji/aliases', [ + 'emoji_id' => $emoji2->id, + 'alias' => ':existing_emoji:', + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['alias']); + }); + + test('can update alias', function () { + $category = EmojiCategory::factory()->create(); + $emoji1 = Emoji::factory()->create(['emoji_category_id' => $category->id]); + $emoji2 = Emoji::factory()->create(['emoji_category_id' => $category->id]); + $alias = EmojiAlias::factory()->create(['emoji_id' => $emoji1->id]); + + $data = [ + 'emoji_id' => $emoji2->id, + 'alias' => ':updated_alias:', + ]; + + $response = $this->patchJson("/api/emoji/aliases/{$alias->id}", $data); + + $response->assertOk() + ->assertJsonPath('data.alias', ':updated_alias:'); + + $this->assertDatabaseHas('emoji_aliases', [ + 'id' => $alias->id, + 'emoji_id' => $emoji2->id, + 'alias' => ':updated_alias:', + ]); + }); + + test('can partially update alias', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + $alias = EmojiAlias::factory()->create([ + 'emoji_id' => $emoji->id, + 'alias' => ':original:', + ]); + + $response = $this->patchJson("/api/emoji/aliases/{$alias->id}", [ + 'alias' => ':updated:', + ]); + + $response->assertOk() + ->assertJsonPath('data.alias', ':updated:'); + + // emoji_id should remain unchanged + $this->assertDatabaseHas('emoji_aliases', [ + 'id' => $alias->id, + 'emoji_id' => $emoji->id, + 'alias' => ':updated:', + ]); + }); + + test('can delete alias', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + $alias = EmojiAlias::factory()->create(['emoji_id' => $emoji->id]); + + $response = $this->deleteJson("/api/emoji/aliases/{$alias->id}"); + + $response->assertNoContent(); + + $this->assertDatabaseMissing('emoji_aliases', ['id' => $alias->id]); + + // Emoji should still exist + $this->assertDatabaseHas('emojis', ['id' => $emoji->id]); + }); + + test('supports pagination', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + EmojiAlias::factory()->count(60)->create(['emoji_id' => $emoji->id]); + + $response = $this->getJson('/api/emoji/aliases?per_page=20&page=2'); + + $response->assertOk() + ->assertJsonPath('meta.current_page', 2) + ->assertJsonPath('meta.per_page', 20) + ->assertJsonCount(20, 'data'); + }); + + test('aliases are ordered alphabetically', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + EmojiAlias::factory()->create(['emoji_id' => $emoji->id, 'alias' => ':zebra:']); + EmojiAlias::factory()->create(['emoji_id' => $emoji->id, 'alias' => ':apple:']); + EmojiAlias::factory()->create(['emoji_id' => $emoji->id, 'alias' => ':banana:']); + + $response = $this->getJson('/api/emoji/aliases'); + + $response->assertOk() + ->assertJsonPath('data.0.alias', ':apple:') + ->assertJsonPath('data.1.alias', ':banana:') + ->assertJsonPath('data.2.alias', ':zebra:'); + }); +}); + +describe('Emoji Alias Search API', function () { + test('can search aliases using scout', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + EmojiAlias::factory()->create([ + 'emoji_id' => $emoji->id, + 'alias' => ':happy:', + ]); + + EmojiAlias::factory()->create([ + 'emoji_id' => $emoji->id, + 'alias' => ':sad:', + ]); + + $response = $this->getJson('/api/emoji/aliases/search?q=happy'); + + $response->assertOk() + ->assertJsonStructure([ + 'data' => [ + '*' => [ + 'id', + 'alias', + 'emoji', + ], + ], + ]); + }); + + test('validates search query parameter', function () { + $response = $this->getJson('/api/emoji/aliases/search'); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['q']); + }); + + test('validates search limit parameter', function () { + $response = $this->getJson('/api/emoji/aliases/search?q=test&limit=150'); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['limit']); + }); + + test('can include emoji data in search results', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + $alias = EmojiAlias::factory()->create(['emoji_id' => $emoji->id]); + + $response = $this->getJson('/api/emoji/aliases/search?q=test&with_emoji=1'); + + $response->assertOk() + ->assertJsonStructure([ + 'data' => [ + '*' => [ + 'emoji' => [ + 'id', + 'title', + 'emoji_shortcode', + ], + ], + ], + ]); + }); +}); diff --git a/tests/Feature/Api/EmojiCategoryTest.php b/tests/Feature/Api/EmojiCategoryTest.php new file mode 100644 index 000000000..09f35b77a --- /dev/null +++ b/tests/Feature/Api/EmojiCategoryTest.php @@ -0,0 +1,222 @@ +count(3)->create(); + + $response = $this->getJson('/api/emoji/categories'); + + $response->assertOk() + ->assertJsonStructure([ + 'data' => [ + '*' => [ + 'id', + 'title', + 'display_order', + 'created_at', + 'updated_at', + ], + ], + ]) + ->assertJsonCount(3, 'data'); + }); + + test('can list categories with emoji counts', function () { + $category1 = EmojiCategory::factory()->create(['title' => 'Category 1']); + $category2 = EmojiCategory::factory()->create(['title' => 'Category 2']); + + Emoji::factory()->count(5)->create(['emoji_category_id' => $category1->id]); + Emoji::factory()->count(3)->create(['emoji_category_id' => $category2->id]); + + $response = $this->getJson('/api/emoji/categories?with_emojis=1'); + + $response->assertOk() + ->assertJsonPath('data.0.emojis_count', 5) + ->assertJsonPath('data.1.emojis_count', 3); + }); + + test('can list categories with full emoji data', function () { + $category = EmojiCategory::factory()->create(); + Emoji::factory()->count(2)->create(['emoji_category_id' => $category->id]); + + $response = $this->getJson('/api/emoji/categories?include_emojis=1'); + + $response->assertOk() + ->assertJsonCount(2, 'data.0.emojis') + ->assertJsonStructure([ + 'data' => [ + '*' => [ + 'emojis' => [ + '*' => [ + 'id', + 'title', + 'emoji_shortcode', + 'emoji_text', + ], + ], + ], + ], + ]); + }); + + test('can get specific category', function () { + $category = EmojiCategory::factory()->create([ + 'title' => 'Test Category', + 'display_order' => 5, + ]); + + $response = $this->getJson("/api/emoji/categories/{$category->id}"); + + $response->assertOk() + ->assertJsonPath('data.title', 'Test Category') + ->assertJsonPath('data.display_order', 5) + ->assertJsonStructure([ + 'data' => [ + 'id', + 'title', + 'display_order', + 'emojis', + 'created_at', + 'updated_at', + ], + ]); + }); + + test('can get category with emoji aliases', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + $response = $this->getJson("/api/emoji/categories/{$category->id}?with_aliases=1"); + + $response->assertOk() + ->assertJsonStructure([ + 'data' => [ + 'emojis' => [ + '*' => [ + 'aliases', + ], + ], + ], + ]); + }); + + test('returns 404 for non-existent category', function () { + $response = $this->getJson('/api/emoji/categories/999'); + + $response->assertNotFound(); + }); + + test('can create category', function () { + $data = [ + 'title' => 'New Category', + 'display_order' => 10, + ]; + + $response = $this->postJson('/api/emoji/categories', $data); + + $response->assertCreated() + ->assertJsonPath('data.title', 'New Category') + ->assertJsonPath('data.display_order', 10); + + $this->assertDatabaseHas('emoji_categories', [ + 'title' => 'New Category', + 'display_order' => 10, + ]); + }); + + test('validates required fields when creating category', function () { + $response = $this->postJson('/api/emoji/categories', []); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['title', 'display_order']); + }); + + test('validates display order is non-negative', function () { + $response = $this->postJson('/api/emoji/categories', [ + 'title' => 'Test Category', + 'display_order' => -1, + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['display_order']); + }); + + test('validates title length', function () { + $response = $this->postJson('/api/emoji/categories', [ + 'title' => str_repeat('a', 256), + 'display_order' => 1, + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['title']); + }); + + test('can update category', function () { + $category = EmojiCategory::factory()->create(); + + $data = [ + 'title' => 'Updated Title', + 'display_order' => 99, + ]; + + $response = $this->patchJson("/api/emoji/categories/{$category->id}", $data); + + $response->assertOk() + ->assertJsonPath('data.title', 'Updated Title') + ->assertJsonPath('data.display_order', 99); + + $this->assertDatabaseHas('emoji_categories', [ + 'id' => $category->id, + 'title' => 'Updated Title', + 'display_order' => 99, + ]); + }); + + test('can partially update category', function () { + $category = EmojiCategory::factory()->create([ + 'title' => 'Original Title', + 'display_order' => 5, + ]); + + $response = $this->patchJson("/api/emoji/categories/{$category->id}", [ + 'title' => 'New Title', + ]); + + $response->assertOk() + ->assertJsonPath('data.title', 'New Title') + ->assertJsonPath('data.display_order', 5); // Should remain unchanged + }); + + test('can delete category', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + $response = $this->deleteJson("/api/emoji/categories/{$category->id}"); + + $response->assertNoContent(); + + $this->assertDatabaseMissing('emoji_categories', ['id' => $category->id]); + + // Emoji should still exist but with null category_id + $this->assertDatabaseHas('emojis', [ + 'id' => $emoji->id, + 'emoji_category_id' => null, + ]); + }); + + test('categories are ordered by display_order', function () { + EmojiCategory::factory()->create(['title' => 'Third', 'display_order' => 3]); + EmojiCategory::factory()->create(['title' => 'First', 'display_order' => 1]); + EmojiCategory::factory()->create(['title' => 'Second', 'display_order' => 2]); + + $response = $this->getJson('/api/emoji/categories'); + + $response->assertOk() + ->assertJsonPath('data.0.title', 'First') + ->assertJsonPath('data.1.title', 'Second') + ->assertJsonPath('data.2.title', 'Third'); + }); +}); diff --git a/tests/Feature/Api/EmojiTest.php b/tests/Feature/Api/EmojiTest.php new file mode 100644 index 000000000..739a28bcb --- /dev/null +++ b/tests/Feature/Api/EmojiTest.php @@ -0,0 +1,333 @@ +create(); + $emojis = Emoji::factory()->count(3)->create(['emoji_category_id' => $category->id]); + + $response = $this->getJson('/api/emoji/emojis'); + + $response->assertOk() + ->assertJsonStructure([ + 'data' => [ + '*' => [ + 'id', + 'title', + 'emoji_text', + 'emoji_shortcode', + 'image_url', + 'sprite_mode', + 'sprite_params', + 'display_order', + 'created_at', + 'updated_at', + ], + ], + 'links', + 'meta', + ]) + ->assertJsonCount(3, 'data'); + }); + + test('can filter emojis by category', function () { + $category1 = EmojiCategory::factory()->create(); + $category2 = EmojiCategory::factory()->create(); + + Emoji::factory()->count(2)->create(['emoji_category_id' => $category1->id]); + Emoji::factory()->count(3)->create(['emoji_category_id' => $category2->id]); + + $response = $this->getJson("/api/emoji/emojis?category_id={$category1->id}"); + + $response->assertOk() + ->assertJsonCount(2, 'data'); + }); + + test('can search emojis', function () { + $category = EmojiCategory::factory()->create(); + Emoji::factory()->create([ + 'title' => 'Happy Face', + 'emoji_shortcode' => ':happy:', + 'emoji_category_id' => $category->id, + ]); + Emoji::factory()->create([ + 'title' => 'Sad Face', + 'emoji_shortcode' => ':sad:', + 'emoji_category_id' => $category->id, + ]); + + $response = $this->getJson('/api/emoji/emojis?search=happy'); + + $response->assertOk() + ->assertJsonCount(1, 'data') + ->assertJsonPath('data.0.title', 'Happy Face'); + }); + + test('can include category and aliases with emojis', function () { + $category = EmojiCategory::factory()->create(['title' => 'Test Category']); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + EmojiAlias::factory()->count(2)->create(['emoji_id' => $emoji->id]); + + $response = $this->getJson('/api/emoji/emojis?with_category=1&with_aliases=1'); + + $response->assertOk() + ->assertJsonPath('data.0.category.title', 'Test Category') + ->assertJsonCount(2, 'data.0.aliases'); + }); + + test('can get specific emoji', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create([ + 'title' => 'Test Emoji', + 'emoji_shortcode' => ':test:', + 'emoji_category_id' => $category->id, + ]); + + $response = $this->getJson("/api/emoji/emojis/{$emoji->id}"); + + $response->assertOk() + ->assertJsonPath('data.title', 'Test Emoji') + ->assertJsonPath('data.emoji_shortcode', ':test:') + ->assertJsonStructure([ + 'data' => [ + 'id', + 'title', + 'emoji_shortcode', + 'category', + 'aliases', + ], + ]); + }); + + test('returns 404 for non-existent emoji', function () { + $response = $this->getJson('/api/emoji/emojis/999'); + + $response->assertNotFound(); + }); + + test('can create unicode emoji', function () { + $category = EmojiCategory::factory()->create(); + + $data = [ + 'title' => 'Happy Face', + 'emoji_text' => '😊', + 'emoji_shortcode' => ':happy_face:', + 'emoji_category_id' => $category->id, + 'display_order' => 1, + ]; + + $response = $this->postJson('/api/emoji/emojis', $data); + + $response->assertCreated() + ->assertJsonPath('data.title', 'Happy Face') + ->assertJsonPath('data.emoji_text', '😊') + ->assertJsonPath('data.emoji_shortcode', ':happy_face:'); + + $this->assertDatabaseHas('emojis', [ + 'title' => 'Happy Face', + 'emoji_text' => '😊', + 'emoji_shortcode' => ':happy_face:', + ]); + }); + + test('can create custom image emoji', function () { + $category = EmojiCategory::factory()->create(); + + $data = [ + 'title' => 'Custom Emoji', + 'emoji_shortcode' => ':custom:', + 'image_url' => '/emojis/custom/custom.png', + 'emoji_category_id' => $category->id, + 'display_order' => 1, + ]; + + $response = $this->postJson('/api/emoji/emojis', $data); + + $response->assertCreated() + ->assertJsonPath('data.title', 'Custom Emoji') + ->assertJsonPath('data.image_url', '/emojis/custom/custom.png') + ->assertJsonPath('data.emoji_text', null); + }); + + test('can create sprite emoji', function () { + $category = EmojiCategory::factory()->create(); + + $data = [ + 'title' => 'Sprite Emoji', + 'emoji_shortcode' => ':sprite:', + 'sprite_mode' => true, + 'sprite_params' => [ + 'x' => 32, + 'y' => 64, + 'width' => 32, + 'height' => 32, + 'sheet' => 'emoji-sheet-1.png', + ], + 'emoji_category_id' => $category->id, + 'display_order' => 1, + ]; + + $response = $this->postJson('/api/emoji/emojis', $data); + + $response->assertCreated() + ->assertJsonPath('data.sprite_mode', true) + ->assertJsonPath('data.sprite_params.x', 32) + ->assertJsonPath('data.sprite_params.sheet', 'emoji-sheet-1.png'); + }); + + test('validates required fields when creating emoji', function () { + $response = $this->postJson('/api/emoji/emojis', []); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['title', 'emoji_shortcode', 'display_order']); + }); + + test('validates emoji shortcode format', function () { + $category = EmojiCategory::factory()->create(); + + $response = $this->postJson('/api/emoji/emojis', [ + 'title' => 'Test', + 'emoji_shortcode' => 'invalid-format', + 'emoji_category_id' => $category->id, + 'display_order' => 1, + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['emoji_shortcode']); + }); + + test('validates unique emoji shortcode', function () { + $category = EmojiCategory::factory()->create(); + Emoji::factory()->create(['emoji_shortcode' => ':existing:']); + + $response = $this->postJson('/api/emoji/emojis', [ + 'title' => 'Test', + 'emoji_shortcode' => ':existing:', + 'emoji_category_id' => $category->id, + 'display_order' => 1, + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['emoji_shortcode']); + }); + + test('validates sprite params when sprite mode is enabled', function () { + $category = EmojiCategory::factory()->create(); + + $response = $this->postJson('/api/emoji/emojis', [ + 'title' => 'Test', + 'emoji_shortcode' => ':test:', + 'sprite_mode' => true, + 'emoji_category_id' => $category->id, + 'display_order' => 1, + ]); + + $response->assertUnprocessable() + ->assertJsonValidationErrors([ + 'sprite_params.x', + 'sprite_params.y', + 'sprite_params.width', + 'sprite_params.height', + 'sprite_params.sheet', + ]); + }); + + test('can update emoji', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + + $data = [ + 'title' => 'Updated Title', + 'display_order' => 99, + ]; + + $response = $this->patchJson("/api/emoji/emojis/{$emoji->id}", $data); + + $response->assertOk() + ->assertJsonPath('data.title', 'Updated Title') + ->assertJsonPath('data.display_order', 99); + + $this->assertDatabaseHas('emojis', [ + 'id' => $emoji->id, + 'title' => 'Updated Title', + 'display_order' => 99, + ]); + }); + + test('can delete emoji', function () { + $category = EmojiCategory::factory()->create(); + $emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]); + $alias = EmojiAlias::factory()->create(['emoji_id' => $emoji->id]); + + $response = $this->deleteJson("/api/emoji/emojis/{$emoji->id}"); + + $response->assertNoContent(); + + $this->assertDatabaseMissing('emojis', ['id' => $emoji->id]); + $this->assertDatabaseMissing('emoji_aliases', ['id' => $alias->id]); + }); + + test('supports pagination', function () { + $category = EmojiCategory::factory()->create(); + Emoji::factory()->count(60)->create(['emoji_category_id' => $category->id]); + + $response = $this->getJson('/api/emoji/emojis?per_page=20&page=2'); + + $response->assertOk() + ->assertJsonPath('meta.current_page', 2) + ->assertJsonPath('meta.per_page', 20) + ->assertJsonCount(20, 'data'); + }); +}); + +describe('Emoji Search API', function () { + test('can search emojis using scout', function () { + $category = EmojiCategory::factory()->create(); + + // Create emojis with different content + Emoji::factory()->create([ + 'title' => 'Happy Face', + 'emoji_shortcode' => ':happy:', + 'emoji_text' => '😊', + 'emoji_category_id' => $category->id, + ]); + + Emoji::factory()->create([ + 'title' => 'Sad Face', + 'emoji_shortcode' => ':sad:', + 'emoji_text' => '😢', + 'emoji_category_id' => $category->id, + ]); + + $response = $this->getJson('/api/emoji/emojis/search?q=happy'); + + $response->assertOk() + ->assertJsonStructure([ + 'data' => [ + '*' => [ + 'id', + 'title', + 'emoji_shortcode', + 'emoji_text', + ], + ], + ]); + }); + + test('validates search query parameter', function () { + $response = $this->getJson('/api/emoji/emojis/search'); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['q']); + }); + + test('validates search limit parameter', function () { + $response = $this->getJson('/api/emoji/emojis/search?q=test&limit=150'); + + $response->assertUnprocessable() + ->assertJsonValidationErrors(['limit']); + }); +});