diff --git a/app/Models/Mutation.php b/app/Models/Mutation.php index 69056b6..2de4925 100644 --- a/app/Models/Mutation.php +++ b/app/Models/Mutation.php @@ -100,6 +100,27 @@ class Mutation extends Model static::updated(function (Mutation $mutation) { if ($mutation->wasChanged('status')) { + + $status = $mutation->getAttribute('status'); + $user = request()->user(); + $ledger = $mutation->ledger; + $mutationMsg = $mutation->chat->messages()->create([ + 'user_id' => null, + 'content' => $status === 'approved' + ? "uuid}> APPROVED the suggestion \"{$mutation->description}\" for ".($mutation->amount >= 0 ? '+' : '')."{$mutation->amount} points on \"{$ledger->name}\" ledger." + : "uuid}> DENIED the suggestion \"{$mutation->description}\" for ".($mutation->amount >= 0 ? '+' : '')."{$mutation->amount} points on \"{$ledger->name}\" ledger.", + 'subject_id' => $mutation->id, + 'subject_type' => Mutation::class, + ]); + + $dynamic = $ledger->dynamic; + $dynamic->chat->messages()->create([ + 'user_id' => null, + 'content' => $mutationMsg->content, + 'subject_id' => $mutation->id, + 'subject_type' => Mutation::class, + ]); + broadcast(new \App\Events\MutationUpdated($mutation)); } }); diff --git a/tests/Feature/DashboardTest.php b/tests/Feature/DashboardTest.php index fa2cc0d..c63aae1 100644 --- a/tests/Feature/DashboardTest.php +++ b/tests/Feature/DashboardTest.php @@ -18,7 +18,7 @@ test('authenticated users can visit the dashboard', function () { $response = $this->get(route('dashboard')); $response->assertOk(); - $response->assertInertia(fn ($page) => $page->component('Dashboard')->has('unreadEntities')); + $response->assertInertia(fn ($page) => $page->component('Dashboard')->has('unreadDynamics')); }); test('visiting dynamic updates the read cursor', function () { @@ -100,12 +100,12 @@ test('dashboard groups and filters unread entities correctly based on cursor', f // Verify unread grouping structure $response->assertInertia(fn ($page) => $page ->component('Dashboard') - ->where('unreadEntities.0.name', 'Testing Dynamic') - ->where('unreadEntities.0.unread_count', 1) - ->has('unreadEntities.0.context_activities', 1) // Should have old message as context - ->where('unreadEntities.0.context_activities.0.content', 'Old message context') - ->has('unreadEntities.0.new_activities', 1) // Should have unread message - ->where('unreadEntities.0.new_activities.0.content', 'New unread message alert') + ->where('unreadDynamics.0.name', 'Testing Dynamic') + ->where('unreadDynamics.0.unread_count', 1) + ->has('unreadDynamics.0.context_activities', 1) // Should have old message as context + ->where('unreadDynamics.0.context_activities.0.content', 'Old message context') + ->has('unreadDynamics.0.new_activities', 1) // Should have unread message + ->where('unreadDynamics.0.new_activities.0.content', 'New unread message alert') ); // Now visit the Dynamic, which clears the unread count @@ -116,7 +116,7 @@ test('dashboard groups and filters unread entities correctly based on cursor', f $response2->assertOk(); $response2->assertInertia(fn ($page) => $page ->component('Dashboard') - ->has('unreadEntities', 0) + ->has('unreadDynamics', 0) ); Carbon::setTestNow(); // Reset test time diff --git a/tests/Feature/MutationTest.php b/tests/Feature/MutationTest.php index 8758b50..8f637b9 100644 --- a/tests/Feature/MutationTest.php +++ b/tests/Feature/MutationTest.php @@ -115,7 +115,7 @@ test('owner can approve a pending suggestion and it is updated and logged', func // Note: one from boot created (empty or via seeder, but in our factory it starts with 0 messages if not manually logged, // actually our model booted hook creates the chat but doesn't log on boot, the update method creates 1 message) expect($mutationChatMessages->last()->user_id)->toBeNull(); - expect($mutationChatMessages->last()->content)->toBe("Suggestion was APPROVED by uuid}>."); + expect($mutationChatMessages->last()->content)->toBe("uuid}> APPROVED the suggestion \"Polished dungeon floors\" for +20 points on \"{$ledger->name}\" ledger."); $dynamicChatMessages = $dynamic->chat->messages; expect($dynamicChatMessages->last()->user_id)->toBeNull(); diff --git a/tests/Feature/NotificationDispatchTest.php b/tests/Feature/NotificationDispatchTest.php new file mode 100644 index 0000000..26c3723 --- /dev/null +++ b/tests/Feature/NotificationDispatchTest.php @@ -0,0 +1,110 @@ +create(); + $participant = User::factory()->create(); + $dynamic = Dynamic::factory()->create(); + $dynamic->participants()->attach($owner->id, ['role' => 'owner']); + $dynamic->participants()->attach($participant->id, ['role' => 'participant']); + $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]); + + $this->actingAs($participant) + ->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [ + 'description' => 'A test suggestion', + 'amount' => 10, + ]); + + Notification::assertSentTo($owner, NewActivityNotification::class); + Notification::assertNotSentTo($participant, NewActivityNotification::class); + } + + public function test_notification_is_sent_on_mutation_approval() + { + Notification::fake(); + + $owner = User::factory()->create(); + $participant = User::factory()->create(); + $dynamic = Dynamic::factory()->create(); + $dynamic->participants()->attach($owner->id, ['role' => 'owner']); + $dynamic->participants()->attach($participant->id, ['role' => 'participant']); + $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]); + $mutation = Mutation::factory()->create([ + 'ledger_id' => $ledger->id, + 'user_id' => $participant->id, + 'status' => 'pending', + ]); + + $this->actingAs($owner) + ->put(route('dynamics.ledgers.mutations.update', [$dynamic, $ledger, $mutation]), [ + 'status' => 'approved', + ]); + + Notification::assertSentTo($participant, NewActivityNotification::class); + Notification::assertNotSentTo($owner, NewActivityNotification::class); + } + + public function test_notification_is_sent_on_mutation_void() + { + Notification::fake(); + + $owner = User::factory()->create(); + $participant = User::factory()->create(); + $dynamic = Dynamic::factory()->create(); + $dynamic->participants()->attach($owner->id, ['role' => 'owner']); + $dynamic->participants()->attach($participant->id, ['role' => 'participant']); + $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]); + $mutation = Mutation::factory()->create([ + 'ledger_id' => $ledger->id, + 'user_id' => $participant->id, + 'status' => 'approved', + ]); + + $this->actingAs($owner) + ->put(route('dynamics.ledgers.mutations.void', [$dynamic, $ledger, $mutation])); + + Notification::assertSentTo($participant, NewActivityNotification::class); + Notification::assertNotSentTo($owner, NewActivityNotification::class); + } + + public function test_notification_is_sent_to_other_participants_when_owner_adds_mutation() + { + Notification::fake(); + + $owner = User::factory()->create(); + $participant1 = User::factory()->create(); + $participant2 = User::factory()->create(); + $dynamic = Dynamic::factory()->create(); + $dynamic->participants()->attach($owner->id, ['role' => 'owner']); + $dynamic->participants()->attach($participant1->id, ['role' => 'participant']); + $dynamic->participants()->attach($participant2->id, ['role' => 'participant']); + $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]); + + $this->actingAs($owner) + ->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [ + 'description' => 'A test mutation from owner', + 'amount' => 25, + ]); + + Notification::assertSentTo($participant1, NewActivityNotification::class); + Notification::assertSentTo($participant2, NewActivityNotification::class); + Notification::assertNotSentTo($owner, NewActivityNotification::class); + } +}