<?php
namespace Illuminate\Support\Testing\Fakes;
use Closure;
use Exception;
use Illuminate\Contracts\Notifications\Dispatcher as NotificationDispatcher;
use Illuminate\Contracts\Notifications\Factory as NotificationFactory;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\Traits\ReflectsClosures;
use PHPUnit\Framework\Assert as PHPUnit;
class NotificationFake implements NotificationDispatcher, NotificationFactory
{
use Macroable, ReflectsClosures;
/**
* All of the notifications that have been sent.
*
* @var array
*/
protected $notifications = [];
/**
* Locale used when sending notifications.
*
* @var string|null
*/
public $locale;
/**
* Assert if a notification was sent on-demand based on a truth-test callback.
*
* @param string|\Closure $notification
* @param callable|null $callback
* @return void
*
* @throws \Exception
*/
public function assertSentOnDemand($notification, $callback = null)
{
$this->assertSentTo(new AnonymousNotifiable, $notification, $callback);
}
/**
* Assert if a notification was sent based on a truth-test callback.
*
* @param mixed $notifiable
* @param string|\Closure $notification
* @param callable|null $callback
* @return void
*
* @throws \Exception
*/
public function assertSentTo($notifiable, $notification, $callback = null)
{
if (is_array($notifiable) || $notifiable instanceof Collection) {
if (count($notifiable) === 0) {
throw new Exception('No notifiable given.');
}
foreach ($notifiable as $singleNotifiable) {
$this->assertSentTo($singleNotifiable, $notification, $callback);
}
return;
}
if ($notification instanceof Closure) {
[$notification, $callback] = [$this->firstClosureParameterType($notification), $notification];
}
if (is_numeric($callback)) {
return $this->assertSentToTimes($notifiable, $notification, $callback);
}
PHPUnit::assertTrue(
$this->sent($notifiable, $notification, $callback)->count() > 0,
"The expected [{$notification}] notification was not sent."
);
}
/**
* Assert if a notification was sent on-demand a number of times.
*
* @param string $notification
* @param int $times
* @return void
*/
public function assertSentOnDemandTimes($notification, $times = 1)
{
return $this->assertSentToTimes(new AnonymousNotifiable, $notification, $times);
}
/**
* Assert if a notification was sent a number of times.
*
* @param mixed $notifiable
* @param string $notification
* @param int $times
* @return void
*/
public function assertSentToTimes($notifiable, $notification, $times = 1)
{
$count = $this->sent($notifiable, $notification)->count();
PHPUnit::assertSame(
$times, $count,
"Expected [{$notification}] to be sent {$times} times, but was sent {$count} times."
);
}
/**
* Determine if a notification was sent based on a truth-test callback.
*
* @param mixed $notifiable
* @param string|\Closure $notification
* @param callable|null $callback
* @return void
*
* @throws \Exception
*/
public function assertNotSentTo($notifiable, $notification, $callback = null)
{
if (is_array($notifiable) || $notifiable instanceof Collection) {
if (count($notifiable) === 0) {
throw new Exception('No notifiable given.');
}
foreach ($notifiable as $singleNotifiable) {
$this->assertNotSentTo($singleNotifiable, $notification, $callback);
}
return;
}
if ($notification instanceof Closure) {
[$notification, $callback] = [$this->firstClosureParameterType($notification), $notification];
}
PHPUnit::assertCount(
0, $this->sent($notifiable, $notification, $callback),
"The unexpected [{$notification}] notification was sent."
);
}
/**
* Assert that no notifications were sent.
*
* @return void
*/
public function assertNothingSent()
{
PHPUnit::assertEmpty($this->notifications, 'Notifications were sent unexpectedly.');
}
/**
* Assert that no notifications were sent to the given notifiable.
*
* @param mixed $notifiable
* @return void
*
* @throws \Exception
*/
public function assertNothingSentTo($notifiable)
{
if (is_array($notifiable) || $notifiable instanceof Collection) {
if (count($notifiable) === 0) {
throw new Exception('No notifiable given.');
}
foreach ($notifiable as $singleNotifiable) {
$this->assertNothingSentTo($singleNotifiable);
}
return;
}
PHPUnit::assertEmpty(
$this->notifications[get_class($notifiable)][$notifiable->getKey()] ?? [],
'Notifications were sent unexpectedly.',
);
}
/**
* Assert the total amount of times a notification was sent.
*
* @param string $notification
* @param int $expectedCount
* @return void
*/
public function assertSentTimes($notification, $expectedCount)
{
$actualCount = collect($this->notifications)
->flatten(1)
->reduce(fn ($count, $sent) => $count + count($sent[$notification] ?? []), 0);
PHPUnit::assertSame(
$expectedCount, $actualCount,
"Expected [{$notification}] to be sent {$expectedCount} times, but was sent {$actualCount} times."
);
}
/**
* Assert the total count of notification that were sent.
*
* @param int $expectedCount
* @return void
*/
public function assertCount($expectedCount)
{
$actualCount = collect($this->notifications)->flatten(3)->count();
PHPUnit::assertSame(
$expectedCount, $actualCount,
"Expected {$expectedCount} notifications to be sent, but {$actualCount} were sent."
);
}
/**
* Assert the total amount of times a notification was sent.
*
* @param int $expectedCount
* @param string $notification
* @return void
*
* @deprecated Use the assertSentTimes method instead
*/
public function assertTimesSent($expectedCount, $notification)
{
$this->assertSentTimes($notification, $expectedCount);
}
/**
* Get all of the notifications matching a truth-test callback.
*
* @param mixed $notifiable
* @param string $notification
* @param callable|null $callback
* @return \Illuminate\Support\Collection
*/
public function sent($notifiable, $notification, $callback = null)
{
if (! $this->hasSent($notifiable, $notification)) {
return collect();
}
$callback = $callback ?: fn () => true;
$notifications = collect($this->notificationsFor($notifiable, $notification));
return $notifications->filter(
fn ($arguments) => $callback(...array_values($arguments))
)->pluck('notification');
}
/**
* Determine if there are more notifications left to inspect.
*
* @param mixed $notifiable
* @param string $notification
* @return bool
*/
public function hasSent($notifiable, $notification)
{
return ! empty($this->notificationsFor($notifiable, $notification));
}
/**
* Get all of the notifications for a notifiable entity by type.
*
* @param mixed $notifiable
* @param string $notification
* @return array
*/
protected function notificationsFor($notifiable, $notification)
{
return $this->notifications[get_class($notifiable)][$notifiable->getKey()][$notification] ?? [];
}
/**
* Send the given notification to the given notifiable entities.
*
* @param \Illuminate\Support\Collection|array|mixed $notifiables
* @param mixed $notification
* @return void
*/
public function send($notifiables, $notification)
{
$this->sendNow($notifiables, $notification);
}
/**
* Send the given notification immediately.
*
* @param \Illuminate\Support\Collection|array|mixed $notifiables
* @param mixed $notification
* @param array|null $channels
* @return void
*/
public function sendNow($notifiables, $notification, array $channels = null)
{
if (! $notifiables instanceof Collection && ! is_array($notifiables)) {
$notifiables = [$notifiables];
}
foreach ($notifiables as $notifiable) {
if (! $notification->id) {
$notification->id = Str::uuid()->toString();
}
$notifiableChannels = $channels ?: $notification->via($notifiable);
if (method_exists($notification, 'shouldSend')) {
$notifiableChannels = array_filter(
$notifiableChannels,
fn ($channel) => $notification->shouldSend($notifiable, $channel) !== false
);
if (empty($notifiableChannels)) {
continue;
}
}
$this->notifications[get_class($notifiable)][$notifiable->getKey()][get_class($notification)][] = [
'notification' => $notification,
'channels' => $notifiableChannels,
'notifiable' => $notifiable,
'locale' => $notification->locale ?? $this->locale ?? value(function () use ($notifiable) {
if ($notifiable instanceof HasLocalePreference) {
return $notifiable->preferredLocale();
}
}),
];
}
}
/**
* Get a channel instance by name.
*
* @param string|null $name
* @return mixed
*/
public function channel($name = null)
{
//
}
/**
* Set the locale of notifications.
*
* @param string $locale
* @return $this
*/
public function locale($locale)
{
$this->locale = $locale;
return $this;
}
/**
* Get the notifications that have been sent.
*
* @return array
*/
public function sentNotifications()
{
return $this->notifications;
}
}