45 - Add Forgejo CI workflow
This commit is contained in:
parent
a8b0ed1dc8
commit
c4ae6c2e69
133 changed files with 3568 additions and 605 deletions
25
.env.testing
Normal file
25
.env.testing
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
APP_NAME=DishPlanner
|
||||||
|
APP_ENV=testing
|
||||||
|
APP_KEY=base64:2ADW3imsmRMo+UDw3GR6852wQu37/aNK1ooxEdaJIC0=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
APP_MAINTENANCE_DRIVER=file
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=4
|
||||||
|
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=single
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
DB_DATABASE=:memory:
|
||||||
|
|
||||||
|
SESSION_DRIVER=array
|
||||||
|
|
||||||
|
BROADCAST_CONNECTION=log
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=sync
|
||||||
|
|
||||||
|
CACHE_STORE=array
|
||||||
|
|
||||||
|
MAIL_MAILER=array
|
||||||
37
.forgejo/workflows/ci.yml
Normal file
37
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['release/*']
|
||||||
|
pull_request:
|
||||||
|
branches: [main, 'release/*']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: forge.lvl0.xyz/lvl0/dishplanner-ci:php8.3-1
|
||||||
|
steps:
|
||||||
|
- uses: https://data.forgejo.org/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Cache Composer dependencies
|
||||||
|
uses: https://data.forgejo.org/actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/composer
|
||||||
|
key: composer-${{ hashFiles('composer.lock') }}
|
||||||
|
restore-keys: composer-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install --no-interaction --prefer-dist
|
||||||
|
|
||||||
|
- name: Prepare environment
|
||||||
|
run: cp .env.testing .env
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: vendor/bin/pint --test
|
||||||
|
|
||||||
|
- name: Static analysis
|
||||||
|
run: vendor/bin/phpstan analyse --memory-limit=1G
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
run: php -d memory_limit=512M vendor/bin/phpunit
|
||||||
|
|
@ -17,11 +17,11 @@ public function execute(array $data): User
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Validate required fields first
|
// Validate required fields first
|
||||||
if (!isset($data['name']) || empty($data['name'])) {
|
if (! isset($data['name']) || empty($data['name'])) {
|
||||||
throw new InvalidArgumentException('Name is required');
|
throw new InvalidArgumentException('Name is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($data['planner_id']) || empty($data['planner_id'])) {
|
if (! isset($data['planner_id']) || empty($data['planner_id'])) {
|
||||||
throw new InvalidArgumentException('Planner ID is required');
|
throw new InvalidArgumentException('Planner ID is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ public function execute(array $data): User
|
||||||
'planner_id' => $data['planner_id'],
|
'planner_id' => $data['planner_id'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!$user) {
|
if (! $user) {
|
||||||
throw new Exception('User creation returned null');
|
throw new Exception('User creation returned null');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ public function execute(array $data): User
|
||||||
|
|
||||||
// Verify the user was actually created
|
// Verify the user was actually created
|
||||||
$createdUser = User::find($user->id);
|
$createdUser = User::find($user->id);
|
||||||
if (!$createdUser) {
|
if (! $createdUser) {
|
||||||
throw new Exception('User creation did not persist to database');
|
throw new Exception('User creation did not persist to database');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,52 +12,52 @@ public function execute(User $user, array $data): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
Log::info('EditUserAction: Starting user update', [
|
Log::info('EditUserAction: Starting user update', [
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'old_name' => $user->name,
|
'old_name' => $user->name,
|
||||||
'new_name' => $data['name'],
|
'new_name' => $data['name'],
|
||||||
'planner_id' => $user->planner_id,
|
'planner_id' => $user->planner_id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$result = $user->update([
|
$result = $user->update([
|
||||||
'name' => $data['name'],
|
'name' => $data['name'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Log::info('EditUserAction: Update result', [
|
Log::info('EditUserAction: Update result', [
|
||||||
'result' => $result,
|
'result' => $result,
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!$result) {
|
if (! $result) {
|
||||||
throw new \Exception('User update returned false');
|
throw new \Exception('User update returned false');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the update actually happened
|
// Verify the update actually happened
|
||||||
$user->refresh();
|
$user->refresh();
|
||||||
if ($user->name !== $data['name']) {
|
if ($user->name !== $data['name']) {
|
||||||
throw new \Exception('User update did not persist to database');
|
throw new \Exception('User update did not persist to database');
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::commit();
|
DB::commit();
|
||||||
|
|
||||||
Log::info('EditUserAction: User successfully updated', [
|
Log::info('EditUserAction: User successfully updated', [
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'updated_name' => $user->name,
|
'updated_name' => $user->name,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
|
|
||||||
Log::error('EditUserAction: User update failed', [
|
Log::error('EditUserAction: User update failed', [
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'trace' => $e->getTraceAsString(),
|
'trace' => $e->getTraceAsString(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ public function handle(): int
|
||||||
|
|
||||||
if ($planners->isEmpty()) {
|
if ($planners->isEmpty()) {
|
||||||
$this->warn('No planners found. Aborting schedule generation.');
|
$this->warn('No planners found. Aborting schedule generation.');
|
||||||
|
|
||||||
return self::FAILURE;
|
return self::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ public function response(
|
||||||
?array $payload = null,
|
?array $payload = null,
|
||||||
array|string|null $errors = null,
|
array|string|null $errors = null,
|
||||||
int $statusCode = 200,
|
int $statusCode = 200,
|
||||||
): JsonResponse
|
): JsonResponse {
|
||||||
{
|
|
||||||
return response()->json(resolve(OutputService::class)->response($success, $payload, $errors), $statusCode);
|
return response()->json(resolve(OutputService::class)->response($success, $payload, $errors), $statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,4 +41,4 @@ public function logout(Request $request)
|
||||||
|
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,4 @@ public function register(Request $request)
|
||||||
|
|
||||||
return redirect(route('dashboard'));
|
return redirect(route('dashboard'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public function toArray(Request $request): array
|
||||||
'recurrences' => $this->recurrences->map(fn ($recurrence) => [
|
'recurrences' => $this->recurrences->map(fn ($recurrence) => [
|
||||||
'id' => $recurrence->id,
|
'id' => $recurrence->id,
|
||||||
'type' => $recurrence->recurrence_type,
|
'type' => $recurrence->recurrence_type,
|
||||||
'value' => $recurrence->getValue()
|
'value' => $recurrence->getValue(),
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,20 @@ class DishesList extends Component
|
||||||
use WithPagination;
|
use WithPagination;
|
||||||
|
|
||||||
public $showCreateModal = false;
|
public $showCreateModal = false;
|
||||||
|
|
||||||
public $showEditModal = false;
|
public $showEditModal = false;
|
||||||
|
|
||||||
public $showDeleteModal = false;
|
public $showDeleteModal = false;
|
||||||
|
|
||||||
public $editingDish = null;
|
public $editingDish = null;
|
||||||
|
|
||||||
public $deletingDish = null;
|
public $deletingDish = null;
|
||||||
|
|
||||||
// Form fields
|
// Form fields
|
||||||
public $name = '';
|
public $name = '';
|
||||||
|
|
||||||
public $selectedUsers = [];
|
public $selectedUsers = [];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'selectedUsers' => 'array',
|
'selectedUsers' => 'array',
|
||||||
|
|
@ -32,14 +36,14 @@ public function render()
|
||||||
$dishes = Dish::with('users')
|
$dishes = Dish::with('users')
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->paginate(10);
|
->paginate(10);
|
||||||
|
|
||||||
$users = User::where('planner_id', auth()->id())
|
$users = User::where('planner_id', auth()->id())
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return view('livewire.dishes.dishes-list', [
|
return view('livewire.dishes.dishes-list', [
|
||||||
'dishes' => $dishes,
|
'dishes' => $dishes,
|
||||||
'users' => $users
|
'users' => $users,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,13 +64,13 @@ public function store()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Attach selected users
|
// Attach selected users
|
||||||
if (!empty($this->selectedUsers)) {
|
if (! empty($this->selectedUsers)) {
|
||||||
$dish->users()->attach($this->selectedUsers);
|
$dish->users()->attach($this->selectedUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->showCreateModal = false;
|
$this->showCreateModal = false;
|
||||||
$this->reset(['name', 'selectedUsers']);
|
$this->reset(['name', 'selectedUsers']);
|
||||||
|
|
||||||
session()->flash('success', 'Dish created successfully.');
|
session()->flash('success', 'Dish created successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,13 +90,13 @@ public function update()
|
||||||
$this->editingDish->update([
|
$this->editingDish->update([
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Sync users
|
// Sync users
|
||||||
$this->editingDish->users()->sync($this->selectedUsers);
|
$this->editingDish->users()->sync($this->selectedUsers);
|
||||||
|
|
||||||
$this->showEditModal = false;
|
$this->showEditModal = false;
|
||||||
$this->reset(['name', 'selectedUsers', 'editingDish']);
|
$this->reset(['name', 'selectedUsers', 'editingDish']);
|
||||||
|
|
||||||
session()->flash('success', 'Dish updated successfully.');
|
session()->flash('success', 'Dish updated successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,7 +112,7 @@ public function delete()
|
||||||
$this->deletingDish->delete();
|
$this->deletingDish->delete();
|
||||||
$this->showDeleteModal = false;
|
$this->showDeleteModal = false;
|
||||||
$this->deletingDish = null;
|
$this->deletingDish = null;
|
||||||
|
|
||||||
session()->flash('success', 'Dish deleted successfully.');
|
session()->flash('success', 'Dish deleted successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +130,7 @@ public function toggleAllUsers(): void
|
||||||
if (count($this->selectedUsers) === $users->count()) {
|
if (count($this->selectedUsers) === $users->count()) {
|
||||||
$this->selectedUsers = [];
|
$this->selectedUsers = [];
|
||||||
} else {
|
} else {
|
||||||
$this->selectedUsers = $users->pluck('id')->map(fn($id) => (string) $id)->toArray();
|
$this->selectedUsers = $users->pluck('id')->map(fn ($id) => (string) $id)->toArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,25 +19,39 @@
|
||||||
class ScheduleCalendar extends Component
|
class ScheduleCalendar extends Component
|
||||||
{
|
{
|
||||||
public $currentMonth;
|
public $currentMonth;
|
||||||
|
|
||||||
public $currentYear;
|
public $currentYear;
|
||||||
|
|
||||||
public $calendarDays = [];
|
public $calendarDays = [];
|
||||||
|
|
||||||
public $showRegenerateModal = false;
|
public $showRegenerateModal = false;
|
||||||
|
|
||||||
public $regenerateDate = null;
|
public $regenerateDate = null;
|
||||||
|
|
||||||
public $regenerateUserId = null;
|
public $regenerateUserId = null;
|
||||||
|
|
||||||
// Edit dish modal
|
// Edit dish modal
|
||||||
public $showEditDishModal = false;
|
public $showEditDishModal = false;
|
||||||
|
|
||||||
public $editDate = null;
|
public $editDate = null;
|
||||||
|
|
||||||
public $editUserId = null;
|
public $editUserId = null;
|
||||||
|
|
||||||
public $selectedDishId = null;
|
public $selectedDishId = null;
|
||||||
|
|
||||||
public $availableDishes = [];
|
public $availableDishes = [];
|
||||||
|
|
||||||
// Add dish modal
|
// Add dish modal
|
||||||
public $showAddDishModal = false;
|
public $showAddDishModal = false;
|
||||||
|
|
||||||
public $addDate = null;
|
public $addDate = null;
|
||||||
|
|
||||||
public $addUserIds = [];
|
public $addUserIds = [];
|
||||||
|
|
||||||
public $addSelectedDishId = null;
|
public $addSelectedDishId = null;
|
||||||
|
|
||||||
public $addAvailableUsers = [];
|
public $addAvailableUsers = [];
|
||||||
|
|
||||||
public $addAvailableDishes = [];
|
public $addAvailableDishes = [];
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
|
|
@ -61,7 +75,7 @@ public function refreshCalendar(): void
|
||||||
|
|
||||||
public function loadCalendar(): void
|
public function loadCalendar(): void
|
||||||
{
|
{
|
||||||
$service = new ScheduleCalendarService();
|
$service = new ScheduleCalendarService;
|
||||||
$this->calendarDays = $service->getCalendarDays(
|
$this->calendarDays = $service->getCalendarDays(
|
||||||
auth()->user(),
|
auth()->user(),
|
||||||
$this->currentMonth,
|
$this->currentMonth,
|
||||||
|
|
@ -93,8 +107,9 @@ public function nextMonth(): void
|
||||||
|
|
||||||
public function regenerateForUserDate($date, $userId): void
|
public function regenerateForUserDate($date, $userId): void
|
||||||
{
|
{
|
||||||
if (!$this->authorizeUser($userId)) {
|
if (! $this->authorizeUser($userId)) {
|
||||||
session()->flash('error', 'Unauthorized action.');
|
session()->flash('error', 'Unauthorized action.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,12 +121,13 @@ public function regenerateForUserDate($date, $userId): void
|
||||||
public function confirmRegenerate(): void
|
public function confirmRegenerate(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->authorizeUser($this->regenerateUserId)) {
|
if (! $this->authorizeUser($this->regenerateUserId)) {
|
||||||
session()->flash('error', 'Unauthorized action.');
|
session()->flash('error', 'Unauthorized action.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$action = new DeleteScheduledUserDishForDateAction();
|
$action = new DeleteScheduledUserDishForDateAction;
|
||||||
$action->execute(
|
$action->execute(
|
||||||
auth()->user(),
|
auth()->user(),
|
||||||
Carbon::parse($this->regenerateDate),
|
Carbon::parse($this->regenerateDate),
|
||||||
|
|
@ -131,12 +147,13 @@ public function confirmRegenerate(): void
|
||||||
public function skipDay($date, $userId): void
|
public function skipDay($date, $userId): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->authorizeUser($userId)) {
|
if (! $this->authorizeUser($userId)) {
|
||||||
session()->flash('error', 'Unauthorized action.');
|
session()->flash('error', 'Unauthorized action.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$action = new SkipScheduledUserDishForDateAction();
|
$action = new SkipScheduledUserDishForDateAction;
|
||||||
$action->execute(
|
$action->execute(
|
||||||
auth()->user(),
|
auth()->user(),
|
||||||
Carbon::parse($date),
|
Carbon::parse($date),
|
||||||
|
|
@ -155,6 +172,7 @@ public function skipDay($date, $userId): void
|
||||||
private function authorizeUser(int $userId): bool
|
private function authorizeUser(int $userId): bool
|
||||||
{
|
{
|
||||||
$user = User::find($userId);
|
$user = User::find($userId);
|
||||||
|
|
||||||
return $user && $user->planner_id === auth()->id();
|
return $user && $user->planner_id === auth()->id();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,8 +197,9 @@ public function cancel(): void
|
||||||
public function removeDish($date, $userId): void
|
public function removeDish($date, $userId): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->authorizeUser($userId)) {
|
if (! $this->authorizeUser($userId)) {
|
||||||
session()->flash('error', 'Unauthorized action.');
|
session()->flash('error', 'Unauthorized action.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,7 +242,7 @@ public function toggleAllUsers(): void
|
||||||
if (count($this->addUserIds) === count($this->addAvailableUsers)) {
|
if (count($this->addUserIds) === count($this->addAvailableUsers)) {
|
||||||
$this->addUserIds = [];
|
$this->addUserIds = [];
|
||||||
} else {
|
} else {
|
||||||
$this->addUserIds = $this->addAvailableUsers->pluck('id')->map(fn($id) => (string) $id)->toArray();
|
$this->addUserIds = $this->addAvailableUsers->pluck('id')->map(fn ($id) => (string) $id)->toArray();
|
||||||
}
|
}
|
||||||
$this->updateAvailableDishes();
|
$this->updateAvailableDishes();
|
||||||
}
|
}
|
||||||
|
|
@ -252,11 +271,13 @@ public function saveAddDish(): void
|
||||||
try {
|
try {
|
||||||
if (empty($this->addUserIds)) {
|
if (empty($this->addUserIds)) {
|
||||||
session()->flash('error', 'Please select at least one user.');
|
session()->flash('error', 'Please select at least one user.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->addSelectedDishId) {
|
if (! $this->addSelectedDishId) {
|
||||||
session()->flash('error', 'Please select a dish.');
|
session()->flash('error', 'Please select a dish.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,8 +294,9 @@ public function saveAddDish(): void
|
||||||
$skippedCount = 0;
|
$skippedCount = 0;
|
||||||
|
|
||||||
foreach ($this->addUserIds as $userId) {
|
foreach ($this->addUserIds as $userId) {
|
||||||
if (!$this->authorizeUser((int) $userId)) {
|
if (! $this->authorizeUser((int) $userId)) {
|
||||||
$skippedCount++;
|
$skippedCount++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,6 +307,7 @@ public function saveAddDish(): void
|
||||||
|
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
$skippedCount++;
|
$skippedCount++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,8 +316,9 @@ public function saveAddDish(): void
|
||||||
->where('dish_id', $this->addSelectedDishId)
|
->where('dish_id', $this->addSelectedDishId)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (!$userDish) {
|
if (! $userDish) {
|
||||||
$skippedCount++;
|
$skippedCount++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,8 +360,9 @@ private function closeAddDishModal(): void
|
||||||
|
|
||||||
public function editDish($date, $userId): void
|
public function editDish($date, $userId): void
|
||||||
{
|
{
|
||||||
if (!$this->authorizeUser($userId)) {
|
if (! $this->authorizeUser($userId)) {
|
||||||
session()->flash('error', 'Unauthorized action.');
|
session()->flash('error', 'Unauthorized action.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,13 +395,15 @@ public function editDish($date, $userId): void
|
||||||
public function saveDish(): void
|
public function saveDish(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!$this->authorizeUser($this->editUserId)) {
|
if (! $this->authorizeUser($this->editUserId)) {
|
||||||
session()->flash('error', 'Unauthorized action.');
|
session()->flash('error', 'Unauthorized action.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->selectedDishId) {
|
if (! $this->selectedDishId) {
|
||||||
session()->flash('error', 'Please select a dish.');
|
session()->flash('error', 'Please select a dish.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,8 +421,9 @@ public function saveDish(): void
|
||||||
->where('dish_id', $this->selectedDishId)
|
->where('dish_id', $this->selectedDishId)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (!$userDish) {
|
if (! $userDish) {
|
||||||
session()->flash('error', 'This dish is not assigned to this user.');
|
session()->flash('error', 'This dish is not assigned to this user.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -427,7 +455,8 @@ public function saveDish(): void
|
||||||
|
|
||||||
public function getMonthNameProperty(): string
|
public function getMonthNameProperty(): string
|
||||||
{
|
{
|
||||||
$service = new ScheduleCalendarService();
|
$service = new ScheduleCalendarService;
|
||||||
|
|
||||||
return $service->getMonthName($this->currentMonth, $this->currentYear);
|
return $service->getMonthName($this->currentMonth, $this->currentYear);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,27 @@
|
||||||
use DishPlanner\Schedule\Actions\ClearScheduleForMonthAction;
|
use DishPlanner\Schedule\Actions\ClearScheduleForMonthAction;
|
||||||
use DishPlanner\Schedule\Actions\GenerateScheduleForMonthAction;
|
use DishPlanner\Schedule\Actions\GenerateScheduleForMonthAction;
|
||||||
use DishPlanner\Schedule\Actions\RegenerateScheduleForDateForUsersAction;
|
use DishPlanner\Schedule\Actions\RegenerateScheduleForDateForUsersAction;
|
||||||
|
use Illuminate\Contracts\View\Factory;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class ScheduleGenerator extends Component
|
class ScheduleGenerator extends Component
|
||||||
{
|
{
|
||||||
private const YEARS_IN_PAST = 1;
|
private const YEARS_IN_PAST = 1;
|
||||||
|
|
||||||
private const YEARS_IN_FUTURE = 5;
|
private const YEARS_IN_FUTURE = 5;
|
||||||
|
|
||||||
public $selectedMonth;
|
public $selectedMonth;
|
||||||
|
|
||||||
public $selectedYear;
|
public $selectedYear;
|
||||||
|
|
||||||
public $selectedUsers = [];
|
public $selectedUsers = [];
|
||||||
|
|
||||||
public $clearExisting = true;
|
public $clearExisting = true;
|
||||||
|
|
||||||
public $showAdvancedOptions = false;
|
public $showAdvancedOptions = false;
|
||||||
|
|
||||||
public $isGenerating = false;
|
public $isGenerating = false;
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
|
|
@ -32,7 +40,7 @@ public function mount(): void
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
public function render(): Factory|View
|
||||||
{
|
{
|
||||||
$users = User::where('planner_id', auth()->id())
|
$users = User::where('planner_id', auth()->id())
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
|
|
@ -43,7 +51,7 @@ public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contrac
|
||||||
return view('livewire.schedule.schedule-generator', [
|
return view('livewire.schedule.schedule-generator', [
|
||||||
'users' => $users,
|
'users' => $users,
|
||||||
'months' => $this->getMonthNames(),
|
'months' => $this->getMonthNames(),
|
||||||
'years' => $years
|
'years' => $years,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,13 +60,13 @@ public function generate(): void
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'selectedUsers' => 'required|array|min:1',
|
'selectedUsers' => 'required|array|min:1',
|
||||||
'selectedMonth' => 'required|integer|min:1|max:12',
|
'selectedMonth' => 'required|integer|min:1|max:12',
|
||||||
'selectedYear' => 'required|integer|min:' . (now()->year - self::YEARS_IN_PAST) . '|max:' . (now()->year + self::YEARS_IN_FUTURE),
|
'selectedYear' => 'required|integer|min:'.(now()->year - self::YEARS_IN_PAST).'|max:'.(now()->year + self::YEARS_IN_FUTURE),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->isGenerating = true;
|
$this->isGenerating = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$action = new GenerateScheduleForMonthAction();
|
$action = new GenerateScheduleForMonthAction;
|
||||||
$action->execute(
|
$action->execute(
|
||||||
auth()->user(),
|
auth()->user(),
|
||||||
$this->selectedMonth,
|
$this->selectedMonth,
|
||||||
|
|
@ -70,8 +78,8 @@ public function generate(): void
|
||||||
$this->isGenerating = false;
|
$this->isGenerating = false;
|
||||||
$this->dispatch('schedule-generated');
|
$this->dispatch('schedule-generated');
|
||||||
|
|
||||||
session()->flash('success', 'Schedule generated successfully for ' .
|
session()->flash('success', 'Schedule generated successfully for '.
|
||||||
$this->getSelectedMonthName() . ' ' . $this->selectedYear);
|
$this->getSelectedMonthName().' '.$this->selectedYear);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->isGenerating = false;
|
$this->isGenerating = false;
|
||||||
|
|
@ -83,7 +91,7 @@ public function generate(): void
|
||||||
public function regenerateForDate($date): void
|
public function regenerateForDate($date): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$action = new RegenerateScheduleForDateForUsersAction();
|
$action = new RegenerateScheduleForDateForUsersAction;
|
||||||
$action->execute(
|
$action->execute(
|
||||||
auth()->user(),
|
auth()->user(),
|
||||||
Carbon::parse($date),
|
Carbon::parse($date),
|
||||||
|
|
@ -91,7 +99,7 @@ public function regenerateForDate($date): void
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->dispatch('schedule-generated');
|
$this->dispatch('schedule-generated');
|
||||||
session()->flash('success', 'Schedule regenerated for ' . Carbon::parse($date)->format('M d, Y'));
|
session()->flash('success', 'Schedule regenerated for '.Carbon::parse($date)->format('M d, Y'));
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('Schedule regeneration failed', ['exception' => $e, 'date' => $date]);
|
Log::error('Schedule regeneration failed', ['exception' => $e, 'date' => $date]);
|
||||||
|
|
@ -102,7 +110,7 @@ public function regenerateForDate($date): void
|
||||||
public function clearMonth(): void
|
public function clearMonth(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$action = new ClearScheduleForMonthAction();
|
$action = new ClearScheduleForMonthAction;
|
||||||
$action->execute(
|
$action->execute(
|
||||||
auth()->user(),
|
auth()->user(),
|
||||||
$this->selectedMonth,
|
$this->selectedMonth,
|
||||||
|
|
@ -111,8 +119,8 @@ public function clearMonth(): void
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->dispatch('schedule-generated');
|
$this->dispatch('schedule-generated');
|
||||||
session()->flash('success', 'Schedule cleared for ' .
|
session()->flash('success', 'Schedule cleared for '.
|
||||||
$this->getSelectedMonthName() . ' ' . $this->selectedYear);
|
$this->getSelectedMonthName().' '.$this->selectedYear);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('Clear month failed', ['exception' => $e]);
|
Log::error('Clear month failed', ['exception' => $e]);
|
||||||
|
|
@ -122,7 +130,7 @@ public function clearMonth(): void
|
||||||
|
|
||||||
public function toggleAdvancedOptions()
|
public function toggleAdvancedOptions()
|
||||||
{
|
{
|
||||||
$this->showAdvancedOptions = !$this->showAdvancedOptions;
|
$this->showAdvancedOptions = ! $this->showAdvancedOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getMonthNames(): array
|
private function getMonthNames(): array
|
||||||
|
|
@ -130,7 +138,7 @@ private function getMonthNames(): array
|
||||||
return [
|
return [
|
||||||
1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April',
|
1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April',
|
||||||
5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August',
|
5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August',
|
||||||
9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'
|
9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
namespace App\Livewire\Users;
|
namespace App\Livewire\Users;
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Actions\User\CreateUserAction;
|
use App\Actions\User\CreateUserAction;
|
||||||
use App\Actions\User\DeleteUserAction;
|
use App\Actions\User\DeleteUserAction;
|
||||||
use App\Actions\User\EditUserAction;
|
use App\Actions\User\EditUserAction;
|
||||||
|
use App\Models\User;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
@ -16,10 +16,13 @@ class UsersList extends Component
|
||||||
use WithPagination;
|
use WithPagination;
|
||||||
|
|
||||||
public bool $showCreateModal = false;
|
public bool $showCreateModal = false;
|
||||||
|
|
||||||
public bool $showEditModal = false;
|
public bool $showEditModal = false;
|
||||||
|
|
||||||
public bool $showDeleteModal = false;
|
public bool $showDeleteModal = false;
|
||||||
|
|
||||||
public ?User $editingUser = null;
|
public ?User $editingUser = null;
|
||||||
|
|
||||||
public ?User $deletingUser = null;
|
public ?User $deletingUser = null;
|
||||||
|
|
||||||
// Form fields
|
// Form fields
|
||||||
|
|
@ -36,7 +39,7 @@ public function render(): View
|
||||||
->paginate(10);
|
->paginate(10);
|
||||||
|
|
||||||
return view('livewire.users.users-list', [
|
return view('livewire.users.users-list', [
|
||||||
'users' => $users
|
'users' => $users,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +55,7 @@ public function store(): void
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
(new CreateUserAction())->execute([
|
(new CreateUserAction)->execute([
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'planner_id' => auth()->id(),
|
'planner_id' => auth()->id(),
|
||||||
]);
|
]);
|
||||||
|
|
@ -62,7 +65,7 @@ public function store(): void
|
||||||
|
|
||||||
session()->flash('success', 'User created successfully.');
|
session()->flash('success', 'User created successfully.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
session()->flash('error', 'Failed to create user: ' . $e->getMessage());
|
session()->flash('error', 'Failed to create user: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +82,7 @@ public function update(): void
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
(new EditUserAction())->execute($this->editingUser, ['name' => $this->name]);
|
(new EditUserAction)->execute($this->editingUser, ['name' => $this->name]);
|
||||||
|
|
||||||
$this->showEditModal = false;
|
$this->showEditModal = false;
|
||||||
$this->reset(['name', 'editingUser']);
|
$this->reset(['name', 'editingUser']);
|
||||||
|
|
@ -89,7 +92,7 @@ public function update(): void
|
||||||
// Force component to re-render with fresh data
|
// Force component to re-render with fresh data
|
||||||
$this->resetPage();
|
$this->resetPage();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
session()->flash('error', 'Failed to update user: ' . $e->getMessage());
|
session()->flash('error', 'Failed to update user: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,17 +105,17 @@ public function confirmDelete(User $user): void
|
||||||
public function delete(): void
|
public function delete(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
(new DeleteUserAction())->execute($this->deletingUser);
|
(new DeleteUserAction)->execute($this->deletingUser);
|
||||||
|
|
||||||
$this->showDeleteModal = false;
|
$this->showDeleteModal = false;
|
||||||
$this->deletingUser = null;
|
$this->deletingUser = null;
|
||||||
|
|
||||||
session()->flash('success', 'User deleted successfully.');
|
session()->flash('success', 'User deleted successfully.');
|
||||||
|
|
||||||
// Force component to re-render with fresh data
|
// Force component to re-render with fresh data
|
||||||
$this->resetPage();
|
$this->resetPage();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
session()->flash('error', 'Failed to delete user: ' . $e->getMessage());
|
session()->flash('error', 'Failed to delete user: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
* @property Carbon $updated_at
|
* @property Carbon $updated_at
|
||||||
* @property Collection<User> $users
|
* @property Collection<User> $users
|
||||||
* @property Collection<UserDish> $userDishes
|
* @property Collection<UserDish> $userDishes
|
||||||
|
*
|
||||||
* @method static create(array $data)
|
* @method static create(array $data)
|
||||||
* @method static findOrFail(int $dish_id)
|
* @method static findOrFail(int $dish_id)
|
||||||
* @method static DishFactory factory($count = null, $state = [])
|
* @method static DishFactory factory($count = null, $state = [])
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property static PlannerFactory factory($count = null, $state = [])
|
* @property static PlannerFactory factory($count = null, $state = [])
|
||||||
* @property Collection<User> $users
|
* @property Collection<User> $users
|
||||||
|
*
|
||||||
* @method static first()
|
* @method static first()
|
||||||
* @method static create(array $array)
|
* @method static create(array $array)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@
|
||||||
* @property Dish $dish
|
* @property Dish $dish
|
||||||
* @property User $user
|
* @property User $user
|
||||||
* @property Carbon $date
|
* @property Carbon $date
|
||||||
* @property boolean $is_skipped
|
* @property bool $is_skipped
|
||||||
* @property Collection<ScheduledUserDish> $scheduledUserDishes
|
* @property Collection<ScheduledUserDish> $scheduledUserDishes
|
||||||
|
*
|
||||||
* @method static create(array $array)
|
* @method static create(array $array)
|
||||||
* @method static Builder where(array|Closure|Expression|string $column, mixed $operator = null, mixed $value = null, string $boolean = 'and')
|
* @method static Builder where(array|Closure|Expression|string $column, mixed $operator = null, mixed $value = null, string $boolean = 'and')
|
||||||
* @method static ScheduleFactory factory($count = null, $state = [])
|
* @method static ScheduleFactory factory($count = null, $state = [])
|
||||||
|
|
@ -38,6 +39,8 @@ class Schedule extends Model
|
||||||
|
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $dateFormat = 'Y-m-d';
|
||||||
|
|
||||||
protected $fillable = ['planner_id', 'date', 'is_skipped'];
|
protected $fillable = ['planner_id', 'date', 'is_skipped'];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
* @property int $user_dish_id
|
* @property int $user_dish_id
|
||||||
* @property UserDish $userDish
|
* @property UserDish $userDish
|
||||||
* @property bool $is_skipped
|
* @property bool $is_skipped
|
||||||
|
*
|
||||||
* @method static create(array $array)
|
* @method static create(array $array)
|
||||||
* @method static ScheduledUserDishFactory factory($count = null, $state = [])
|
* @method static ScheduledUserDishFactory factory($count = null, $state = [])
|
||||||
* @method static firstOrCreate(array $array, array $array1)
|
* @method static firstOrCreate(array $array, array $array1)
|
||||||
|
|
@ -29,7 +30,7 @@ class ScheduledUserDish extends Model
|
||||||
'schedule_id',
|
'schedule_id',
|
||||||
'user_id',
|
'user_id',
|
||||||
'user_dish_id',
|
'user_dish_id',
|
||||||
'is_skipped'
|
'is_skipped',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@
|
||||||
use Database\Factories\UserFactory;
|
use Database\Factories\UserFactory;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
* @property string $name
|
* @property string $name
|
||||||
* @property Collection<Dish> $dishes
|
* @property Collection<Dish> $dishes
|
||||||
* @property Collection<UserDish> $userDishes
|
* @property Collection<UserDish> $userDishes
|
||||||
|
*
|
||||||
* @method static User findOrFail(int $user_id)
|
* @method static User findOrFail(int $user_id)
|
||||||
* @method static UserFactory factory($count = null, $state = [])
|
* @method static UserFactory factory($count = null, $state = [])
|
||||||
* @method static create(array $array)
|
* @method static create(array $array)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
* @method static UserDish|null find(int|null $user_dish_id)
|
* @method static UserDish|null find(int|null $user_dish_id)
|
||||||
* @method static create(array $array)
|
* @method static create(array $array)
|
||||||
* @method static where(string $string, int $id)
|
* @method static where(string $string, int $id)
|
||||||
|
*
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property int $dish_id
|
* @property int $dish_id
|
||||||
* @property int $user_id
|
* @property int $user_id
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public function getValue(): int
|
||||||
return match ($this->recurrence_type) {
|
return match ($this->recurrence_type) {
|
||||||
WeeklyRecurrence::class => $this->recurrence->weekday->value,
|
WeeklyRecurrence::class => $this->recurrence->weekday->value,
|
||||||
MinimumRecurrence::class => $this->recurrence->days,
|
MinimumRecurrence::class => $this->recurrence->days,
|
||||||
default => throw new InvalidRecurrenceTypeException()
|
default => throw new InvalidRecurrenceTypeException
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,11 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $weekday
|
* @property int $weekday
|
||||||
|
*
|
||||||
* @method static create(array $array)
|
* @method static create(array $array)
|
||||||
* @method static WeeklyRecurrenceFactory factory($count = null, $state = [])
|
* @method static WeeklyRecurrenceFactory factory($count = null, $state = [])
|
||||||
*/
|
*/
|
||||||
class WeeklyRecurrence extends Model implements RecurrenceInterface, FixedRecurrenceInterface
|
class WeeklyRecurrence extends Model implements FixedRecurrenceInterface, RecurrenceInterface
|
||||||
{
|
{
|
||||||
/** @use HasFactory<WeeklyRecurrenceFactory> */
|
/** @use HasFactory<WeeklyRecurrenceFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as BaseHandler;
|
use Illuminate\Foundation\Exceptions\Handler as BaseHandler;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\Facades\URL;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
|
@ -25,7 +24,8 @@ class AppServiceProvider extends ServiceProvider
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->app->bind(ExceptionHandler::class, function ($app) {
|
$this->app->bind(ExceptionHandler::class, function ($app) {
|
||||||
return new class($app) extends BaseHandler {
|
return new class($app) extends BaseHandler
|
||||||
|
{
|
||||||
public function render($request, Throwable $e)
|
public function render($request, Throwable $e)
|
||||||
{
|
{
|
||||||
// Handle specific custom exception
|
// Handle specific custom exception
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
|
||||||
class OutputService
|
class OutputService
|
||||||
{
|
{
|
||||||
public function response(bool $success = true, ?array $payload = null, array|string|null $errors = null): array
|
public function response(bool $success = true, ?array $payload = null, array|string|null $errors = null): array
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Providers\AppServiceProvider;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
AppServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,14 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
"larastan/larastan": "^3.10",
|
||||||
"laravel/dusk": "^8.3",
|
"laravel/dusk": "^8.3",
|
||||||
"laravel/pail": "^1.1",
|
"laravel/pail": "^1.1",
|
||||||
"laravel/pint": "^1.13",
|
"laravel/pint": "^1.13",
|
||||||
"laravel/sail": "^1.26",
|
"laravel/sail": "^1.26",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nunomaduro/collision": "^8.1",
|
"nunomaduro/collision": "^8.1",
|
||||||
|
"phpstan/phpstan-mockery": "^2.0",
|
||||||
"phpunit/phpunit": "^11.0.1"
|
"phpunit/phpunit": "^11.0.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Planner;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -50,7 +52,7 @@
|
||||||
'providers' => [
|
'providers' => [
|
||||||
'planners' => [
|
'planners' => [
|
||||||
'driver' => 'eloquent',
|
'driver' => 'eloquent',
|
||||||
'model' => App\Models\Planner::class,
|
'model' => Planner::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
// 'users' => [
|
// 'users' => [
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||||
|
use Laravel\Sanctum\Http\Middleware\AuthenticateSession;
|
||||||
use Laravel\Sanctum\Sanctum;
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
@ -75,9 +78,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
'authenticate_session' => AuthenticateSession::class,
|
||||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
'encrypt_cookies' => EncryptCookies::class,
|
||||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
'validate_csrf_token' => ValidateCsrfToken::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
use App\Models\Dish;
|
use App\Models\Dish;
|
||||||
use App\Models\UserDish;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\UserDish;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,4 +73,4 @@ public function run(): void
|
||||||
$this->command->info('Development data seeded successfully!');
|
$this->command->info('Development data seeded successfully!');
|
||||||
$this->command->info('Login credentials: myrmidex@myrmidex.net / Password');
|
$this->command->info('Login credentials: myrmidex@myrmidex.net / Password');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public function run(): void
|
||||||
[
|
[
|
||||||
'name' => 'Admin',
|
'name' => 'Admin',
|
||||||
'email' => 'admin@test.com',
|
'email' => 'admin@test.com',
|
||||||
'password' => 'password'
|
'password' => 'password',
|
||||||
],
|
],
|
||||||
])->each(fn (array $data) => Planner::create([
|
])->each(fn (array $data) => Planner::create([
|
||||||
'name' => $data['name'],
|
'name' => $data['name'],
|
||||||
|
|
|
||||||
|
|
@ -41,17 +41,16 @@ private function createScheduleForPeriod(CarbonPeriod $period): void
|
||||||
$planner = Planner::all()->first() ?? Planner::factory()->create();
|
$planner = Planner::all()->first() ?? Planner::factory()->create();
|
||||||
|
|
||||||
collect($period)
|
collect($period)
|
||||||
->each(fn (Carbon $date) =>
|
->each(fn (Carbon $date) => User::query()
|
||||||
User::query()
|
->inRandomOrder()
|
||||||
->inRandomOrder()
|
->get()
|
||||||
->get()
|
->each(fn (User $user) => (new CreateScheduledUserDishAction)
|
||||||
->each(fn (User $user) => (new CreateScheduledUserDishAction())
|
->execute(
|
||||||
->execute(
|
planner: $planner,
|
||||||
planner: $planner,
|
schedule: resolve(ScheduleRepository::class)->findOrCreate($planner, $date),
|
||||||
schedule: resolve(ScheduleRepository::class)->findOrCreate($planner, $date),
|
userDish: $user->userDishes->random(),
|
||||||
userDish: $user->userDishes->random(),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ public function run(): void
|
||||||
->each(fn (string $name) => User::factory()->create([
|
->each(fn (string $name) => User::factory()->create([
|
||||||
'planner_id' => $planner->id,
|
'planner_id' => $planner->id,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
]))
|
]));
|
||||||
;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2839
phpstan-baseline.neon
Normal file
2839
phpstan-baseline.neon
Normal file
File diff suppressed because it is too large
Load diff
16
phpstan.neon
Normal file
16
phpstan.neon
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
includes:
|
||||||
|
- vendor/larastan/larastan/extension.neon
|
||||||
|
- vendor/phpstan/phpstan-mockery/extension.neon
|
||||||
|
- phpstan-baseline.neon
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
level: 7
|
||||||
|
paths:
|
||||||
|
- app/
|
||||||
|
- src/
|
||||||
|
- tests/
|
||||||
|
|
||||||
|
excludePaths:
|
||||||
|
- bootstrap/*.php
|
||||||
|
- storage/*
|
||||||
|
|
||||||
15
phpunit.xml
15
phpunit.xml
|
|
@ -23,13 +23,6 @@
|
||||||
<directory>app/Providers</directory>
|
<directory>app/Providers</directory>
|
||||||
</exclude>
|
</exclude>
|
||||||
</source>
|
</source>
|
||||||
<coverage>
|
|
||||||
<report>
|
|
||||||
<html outputDirectory="coverage"/>
|
|
||||||
<text outputFile="coverage/coverage.txt" showOnlySummary="true"/>
|
|
||||||
<clover outputFile="coverage/clover.xml"/>
|
|
||||||
</report>
|
|
||||||
</coverage>
|
|
||||||
<php>
|
<php>
|
||||||
<env name="APP_ENV" value="testing"/>
|
<env name="APP_ENV" value="testing"/>
|
||||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||||
|
|
@ -40,12 +33,8 @@
|
||||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||||
<env name="SESSION_DRIVER" value="array"/>
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||||
<env name="DB_CONNECTION" value="mysql"/>
|
<env name="DB_CONNECTION" value="sqlite"/>
|
||||||
<env name="DB_HOST" value="mysql"/>
|
<env name="DB_DATABASE" value=":memory:"/>
|
||||||
<env name="DB_PORT" value="3306"/>
|
|
||||||
<env name="DB_DATABASE" value="testing"/>
|
|
||||||
<env name="DB_USERNAME" value="sail"/>
|
|
||||||
<env name="DB_PASSWORD" value="password"/>
|
|
||||||
|
|
||||||
</php>
|
</php>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
|
||||||
3
pint.json
Normal file
3
pint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"preset": "laravel"
|
||||||
|
}
|
||||||
|
|
@ -5,12 +5,12 @@
|
||||||
Route::group([
|
Route::group([
|
||||||
'as' => 'api.',
|
'as' => 'api.',
|
||||||
], function () {
|
], function () {
|
||||||
require __DIR__ . '/api/auth.php';
|
require __DIR__.'/api/auth.php';
|
||||||
|
|
||||||
Route::middleware('auth:sanctum')->group(function () {
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
require __DIR__ . '/api/users.php';
|
require __DIR__.'/api/users.php';
|
||||||
require __DIR__ . '/api/dishes.php';
|
require __DIR__.'/api/dishes.php';
|
||||||
require __DIR__ . '/api/schedule.php';
|
require __DIR__.'/api/schedule.php';
|
||||||
require __DIR__ . '/api/scheduledUserDishes.php';
|
require __DIR__.'/api/scheduledUserDishes.php';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,3 @@
|
||||||
->json($request->user())
|
->json($request->user())
|
||||||
)->name('me');
|
)->name('me');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
use App\Http\Controllers\Auth\LoginController;
|
use App\Http\Controllers\Auth\LoginController;
|
||||||
use App\Http\Controllers\Auth\RegisterController;
|
use App\Http\Controllers\Auth\RegisterController;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ pkgs.mkShell {
|
||||||
}
|
}
|
||||||
|
|
||||||
dev-test() {
|
dev-test() {
|
||||||
podman-compose exec -T app php -d memory_limit=512M vendor/bin/phpunit "$@"
|
podman-compose exec -T app env $(grep -vE '^\s*(#|$)' .env.testing) php -d memory_limit=512M vendor/bin/phpunit "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
dev-fix-permissions() {
|
dev-fix-permissions() {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public function login(Request $request): JsonResponse
|
||||||
'password' => ['required'],
|
'password' => ['required'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!Auth::attempt($credentials)) {
|
if (! Auth::attempt($credentials)) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'The provided credentials are incorrect.',
|
'message' => 'The provided credentials are incorrect.',
|
||||||
], 401);
|
], 401);
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public function index(): JsonResponse
|
||||||
|
|
||||||
public function store(StoreDishRequest $request): JsonResponse
|
public function store(StoreDishRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$dish = (new CreateDishAction())->execute($request->validated());
|
$dish = (new CreateDishAction)->execute($request->validated());
|
||||||
|
|
||||||
return $this->success(['dish' => new DishResource($dish)]);
|
return $this->success(['dish' => new DishResource($dish)]);
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ public function update(UpdateDishRequest $request, Dish $dish): JsonResponse
|
||||||
{
|
{
|
||||||
Gate::authorize('update', $dish);
|
Gate::authorize('update', $dish);
|
||||||
|
|
||||||
$dish = (new UpdateDishAction())->execute($dish, $request->validated());
|
$dish = (new UpdateDishAction)->execute($dish, $request->validated());
|
||||||
|
|
||||||
return $this->success(['dish' => new DishResource($dish)]);
|
return $this->success(['dish' => new DishResource($dish)]);
|
||||||
}
|
}
|
||||||
|
|
@ -56,14 +56,14 @@ public function destroy(Dish $dish): JsonResponse
|
||||||
{
|
{
|
||||||
Gate::authorize('delete', $dish);
|
Gate::authorize('delete', $dish);
|
||||||
|
|
||||||
(new DeleteDishAction())->execute($dish);
|
(new DeleteDishAction)->execute($dish);
|
||||||
|
|
||||||
return $this->success(null);
|
return $this->success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function syncUsers(SyncUsersRequest $request, Dish $dish): JsonResponse
|
public function syncUsers(SyncUsersRequest $request, Dish $dish): JsonResponse
|
||||||
{
|
{
|
||||||
(new SyncUsersAction())->execute($dish, Arr::get($request->validated(), 'users', []));
|
(new SyncUsersAction)->execute($dish, Arr::get($request->validated(), 'users', []));
|
||||||
|
|
||||||
return $this->success(['dish' => new DishResource($dish->refresh())]);
|
return $this->success(['dish' => new DishResource($dish->refresh())]);
|
||||||
}
|
}
|
||||||
|
|
@ -72,14 +72,14 @@ public function addUsers(AddUsersToDishRequest $request, Dish $dish): JsonRespon
|
||||||
{
|
{
|
||||||
Gate::authorize('update', $dish);
|
Gate::authorize('update', $dish);
|
||||||
|
|
||||||
(new AddUsersToDishAction())->execute($dish, Arr::get($request->validated(), 'users', []));
|
(new AddUsersToDishAction)->execute($dish, Arr::get($request->validated(), 'users', []));
|
||||||
|
|
||||||
return $this->success(['dish' => new DishResource($dish->refresh())]);
|
return $this->success(['dish' => new DishResource($dish->refresh())]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removeUsers(RemoveUsersFromDishRequest $request, Dish $dish): JsonResponse
|
public function removeUsers(RemoveUsersFromDishRequest $request, Dish $dish): JsonResponse
|
||||||
{
|
{
|
||||||
(new RemoveUsersFromDishAction())->execute($dish, Arr::get($request->validated(), 'users', []));
|
(new RemoveUsersFromDishAction)->execute($dish, Arr::get($request->validated(), 'users', []));
|
||||||
|
|
||||||
return $this->success(['dish' => new DishResource($dish->refresh())]);
|
return $this->success(['dish' => new DishResource($dish->refresh())]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,6 @@
|
||||||
class InvalidDishException extends CustomException
|
class InvalidDishException extends CustomException
|
||||||
{
|
{
|
||||||
protected $message = 'INVALID_DISH';
|
protected $message = 'INVALID_DISH';
|
||||||
|
|
||||||
protected $code = 422;
|
protected $code = 422;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class DraftScheduleForDateAction
|
||||||
public function execute(Schedule $schedule): Schedule
|
public function execute(Schedule $schedule): Schedule
|
||||||
{
|
{
|
||||||
User::all()
|
User::all()
|
||||||
->reject(fn($user) => $schedule
|
->reject(fn ($user) => $schedule
|
||||||
->scheduledUserDishes
|
->scheduledUserDishes
|
||||||
->map(fn (ScheduledUserDish $scheduledUserDish) => $scheduledUserDish->userDish?->user)
|
->map(fn (ScheduledUserDish $scheduledUserDish) => $scheduledUserDish->userDish?->user)
|
||||||
->filter()
|
->filter()
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
use App\Models\ScheduledUserDish;
|
use App\Models\ScheduledUserDish;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class GenerateScheduleForMonthAction
|
class GenerateScheduleForMonthAction
|
||||||
|
|
@ -84,7 +83,7 @@ private function generateSchedulesForPeriod(
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($userIds as $userId) {
|
foreach ($userIds as $userId) {
|
||||||
if (!isset($userDishesMap[$userId]) || $userDishesMap[$userId]->isEmpty()) {
|
if (! isset($userDishesMap[$userId]) || $userDishesMap[$userId]->isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public function execute(Planner $planner, Schedule $schedule, User $user, bool $
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$overwrite && $scheduledUserDish->userDish) {
|
if (! $overwrite && $scheduledUserDish->userDish) {
|
||||||
return $scheduledUserDish;
|
return $scheduledUserDish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
use App\Http\Controllers\Api\ApiController;
|
use App\Http\Controllers\Api\ApiController;
|
||||||
use App\Models\Planner;
|
use App\Models\Planner;
|
||||||
use App\Models\Schedule;
|
use App\Models\Schedule;
|
||||||
use Carbon\CarbonPeriod;
|
|
||||||
use DishPlanner\Schedule\Actions\DraftScheduleForPeriodAction;
|
|
||||||
use DishPlanner\Schedule\Actions\GenerateScheduleForPeriodAction;
|
use DishPlanner\Schedule\Actions\GenerateScheduleForPeriodAction;
|
||||||
use DishPlanner\Schedule\Actions\UpdateScheduleAction;
|
use DishPlanner\Schedule\Actions\UpdateScheduleAction;
|
||||||
use DishPlanner\Schedule\Repositories\ScheduleRepository;
|
use DishPlanner\Schedule\Repositories\ScheduleRepository;
|
||||||
|
|
@ -83,7 +81,7 @@ public function generate(GenerateScheduleRequest $request): JsonResponse
|
||||||
/** @var Planner $planner */
|
/** @var Planner $planner */
|
||||||
$planner = auth()->user();
|
$planner = auth()->user();
|
||||||
|
|
||||||
(new GenerateScheduleForPeriodAction())->execute($planner, $request->get('overwrite', false));
|
(new GenerateScheduleForPeriodAction)->execute($planner, $request->get('overwrite', false));
|
||||||
|
|
||||||
return $this->success(null);
|
return $this->success(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ public function __invoke(ScheduleUserDishRequest $request, Carbon $date): JsonRe
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (! $scheduledUserDish) {
|
if (! $scheduledUserDish) {
|
||||||
$scheduledUserDish = new ScheduledUserDish();
|
$scheduledUserDish = new ScheduledUserDish;
|
||||||
}
|
}
|
||||||
|
|
||||||
abort_if(
|
abort_if(
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ public function rules(): array
|
||||||
'user_dish_id' => [
|
'user_dish_id' => [
|
||||||
'required_without:skipped',
|
'required_without:skipped',
|
||||||
'exists:user_dishes,id',
|
'exists:user_dishes,id',
|
||||||
'nullable'
|
'nullable',
|
||||||
],
|
],
|
||||||
'user_id' => ['required', 'exists:users,id'],
|
'user_id' => ['required', 'exists:users,id'],
|
||||||
'skipped' => ['required_if:user_dish_id,null', 'boolean'],
|
'skipped' => ['required_if:user_dish_id,null', 'boolean'],
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property boolean $is_skipped
|
* @property bool $is_skipped
|
||||||
*/
|
*/
|
||||||
class UpdateScheduleRequest extends FormRequest
|
class UpdateScheduleRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ private function buildCalendarDays(int $year, int $month, int $daysInMonth, Coll
|
||||||
'date' => $date,
|
'date' => $date,
|
||||||
'isToday' => $date->isToday(),
|
'isToday' => $date->isToday(),
|
||||||
'scheduledDishes' => $scheduledDishes,
|
'scheduledDishes' => $scheduledDishes,
|
||||||
'isEmpty' => $scheduledDishes->isEmpty()
|
'isEmpty' => $scheduledDishes->isEmpty(),
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$calendarDays[] = [
|
$calendarDays[] = [
|
||||||
|
|
@ -51,7 +51,7 @@ private function buildCalendarDays(int $year, int $month, int $daysInMonth, Coll
|
||||||
'date' => null,
|
'date' => null,
|
||||||
'isToday' => false,
|
'isToday' => false,
|
||||||
'scheduledDishes' => collect(),
|
'scheduledDishes' => collect(),
|
||||||
'isEmpty' => true
|
'isEmpty' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public function generate(Planner $planner): void
|
||||||
$users->each(function (User $user) use ($date, $planner, $scheduleRepository, $userDishRepository) {
|
$users->each(function (User $user) use ($date, $planner, $scheduleRepository, $userDishRepository) {
|
||||||
$schedule = $scheduleRepository->findOrCreate($planner, $date);
|
$schedule = $scheduleRepository->findOrCreate($planner, $date);
|
||||||
|
|
||||||
(new CreateScheduledUserDishAction())->execute(
|
(new CreateScheduledUserDishAction)->execute(
|
||||||
planner: $planner,
|
planner: $planner,
|
||||||
schedule: $schedule,
|
schedule: $schedule,
|
||||||
userDish: $userDishRepository->getRandomForDate($user, $date)
|
userDish: $userDishRepository->getRandomForDate($user, $date)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class CreateScheduledUserDishAction
|
||||||
public function execute(Planner $planner, Schedule $schedule, UserDish $userDish): ScheduledUserDish
|
public function execute(Planner $planner, Schedule $schedule, UserDish $userDish): ScheduledUserDish
|
||||||
{
|
{
|
||||||
if ($userDish->dish->planner_id !== $planner->id || $userDish->user->planner_id !== $planner->id) {
|
if ($userDish->dish->planner_id !== $planner->id || $userDish->user->planner_id !== $planner->id) {
|
||||||
throw new InvalidPlannerException();
|
throw new InvalidPlannerException;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ScheduledUserDish::create([
|
return ScheduledUserDish::create([
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public function create(CreateScheduleRequest $request): JsonResponse
|
||||||
$schedule = resolve(ScheduleRepository::class)->findOrCreate($planner, $date);
|
$schedule = resolve(ScheduleRepository::class)->findOrCreate($planner, $date);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$scheduledUserDish = (new CreateScheduledUserDishAction())->execute(
|
$scheduledUserDish = (new CreateScheduledUserDishAction)->execute(
|
||||||
planner: $planner,
|
planner: $planner,
|
||||||
schedule: $schedule,
|
schedule: $schedule,
|
||||||
userDish: $userDish,
|
userDish: $userDish,
|
||||||
|
|
@ -46,7 +46,7 @@ public function create(CreateScheduleRequest $request): JsonResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'scheduled_user_dish' => new ScheduledUserDishResource($scheduledUserDish)
|
'scheduled_user_dish' => new ScheduledUserDishResource($scheduledUserDish),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ public function update(UpdateScheduledUserDishRequest $request, ScheduledUserDis
|
||||||
{
|
{
|
||||||
Gate::authorize('update', $scheduledUserDish);
|
Gate::authorize('update', $scheduledUserDish);
|
||||||
|
|
||||||
(new UpdateScheduledUserDishAction())->execute(
|
(new UpdateScheduledUserDishAction)->execute(
|
||||||
scheduledUserDish: $scheduledUserDish,
|
scheduledUserDish: $scheduledUserDish,
|
||||||
userDish: UserDish::find($request->user_dish_id),
|
userDish: UserDish::find($request->user_dish_id),
|
||||||
isSkipped: $request->is_skipped ?? null,
|
isSkipped: $request->is_skipped ?? null,
|
||||||
|
|
@ -78,7 +78,7 @@ public function delete(ScheduledUserDish $scheduledUserDish): JsonResponse
|
||||||
{
|
{
|
||||||
Gate::authorize('delete', $scheduledUserDish);
|
Gate::authorize('delete', $scheduledUserDish);
|
||||||
|
|
||||||
(new DeleteScheduledUserDishAction())->execute($scheduledUserDish);
|
(new DeleteScheduledUserDishAction)->execute($scheduledUserDish);
|
||||||
|
|
||||||
return $this->success(null);
|
return $this->success(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
use App\Models\Planner;
|
use App\Models\Planner;
|
||||||
use App\Models\ScheduledUserDish;
|
use App\Models\ScheduledUserDish;
|
||||||
use DishPlanner\UserDish\Policies\UserDishPolicy;
|
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
class ScheduledUserDishPolicy
|
class ScheduledUserDishPolicy
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace DishPlanner\User\Actions;
|
namespace DishPlanner\User\Actions;
|
||||||
|
|
||||||
use App\Models\Planner;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
|
||||||
class DeleteUserAction
|
class DeleteUserAction
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace DishPlanner\User\Actions;
|
namespace DishPlanner\User\Actions;
|
||||||
|
|
||||||
use App\Models\Planner;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
|
||||||
class UpdateUserAction
|
class UpdateUserAction
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ public function create(CreateUserRequest $request): JsonResponse
|
||||||
|
|
||||||
$requestData = $request->validated();
|
$requestData = $request->validated();
|
||||||
|
|
||||||
$user = (new CreateUserAction())
|
$user = (new CreateUserAction)
|
||||||
->execute($planner, Arr::get($requestData, 'name'));
|
->execute($planner, Arr::get($requestData, 'name'));
|
||||||
|
|
||||||
return $this->success(['user' => new UserResource($user)]);
|
return $this->success(['user' => new UserResource($user)]);
|
||||||
|
|
@ -43,7 +43,7 @@ public function update(UpdateUserRequest $request, User $user): JsonResponse
|
||||||
{
|
{
|
||||||
Gate::authorize('update', $user);
|
Gate::authorize('update', $user);
|
||||||
|
|
||||||
$user = (new UpdateUserAction())
|
$user = (new UpdateUserAction)
|
||||||
->execute($user, Arr::get($request->validated(), 'name'));
|
->execute($user, Arr::get($request->validated(), 'name'));
|
||||||
|
|
||||||
return $this->success(['user' => new UserResource($user)]);
|
return $this->success(['user' => new UserResource($user)]);
|
||||||
|
|
@ -53,7 +53,7 @@ public function delete(User $user): JsonResponse
|
||||||
{
|
{
|
||||||
Gate::authorize('delete', $user);
|
Gate::authorize('delete', $user);
|
||||||
|
|
||||||
(new DeleteUserAction())->execute($user);
|
(new DeleteUserAction)->execute($user);
|
||||||
|
|
||||||
return $this->success(null, 201);
|
return $this->success(null, 201);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ class CreateFixedRecurrenceAction
|
||||||
*/
|
*/
|
||||||
public function execute(UserDish $userDish, string $recurrenceType, int $value): void
|
public function execute(UserDish $userDish, string $recurrenceType, int $value): void
|
||||||
{
|
{
|
||||||
if (!in_array($recurrenceType, self::FIXED_RECURRENCES)) {
|
if (! in_array($recurrenceType, self::FIXED_RECURRENCES)) {
|
||||||
throw new InvalidRecurrenceTypeException();
|
throw new InvalidRecurrenceTypeException;
|
||||||
}
|
}
|
||||||
|
|
||||||
$recurrence = $recurrenceType::create([
|
$recurrence = $recurrenceType::create([
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class CreateMinimumRecurrenceAction
|
||||||
public function execute(UserDish $userDish, string $recurrenceType, int $recurrenceValue): void
|
public function execute(UserDish $userDish, string $recurrenceType, int $recurrenceValue): void
|
||||||
{
|
{
|
||||||
if ($recurrenceType !== MinimumRecurrence::class) {
|
if ($recurrenceType !== MinimumRecurrence::class) {
|
||||||
throw new InvalidRecurrenceTypeException();
|
throw new InvalidRecurrenceTypeException;
|
||||||
}
|
}
|
||||||
|
|
||||||
$existingRecurrenceForDay = $userDish
|
$existingRecurrenceForDay = $userDish
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,11 @@ private function addRecurrences(UserDish $userDish, array $data): void
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($recurrenceType === WeeklyRecurrence::class) {
|
if ($recurrenceType === WeeklyRecurrence::class) {
|
||||||
(new CreateFixedRecurrenceAction())->execute($userDish, $recurrenceType, $recurrenceValue);
|
(new CreateFixedRecurrenceAction)->execute($userDish, $recurrenceType, $recurrenceValue);
|
||||||
} elseif ($recurrenceType === MinimumRecurrence::class) {
|
} elseif ($recurrenceType === MinimumRecurrence::class) {
|
||||||
(new CreateMinimumRecurrenceAction())->execute($userDish, $recurrenceType, $recurrenceValue);
|
(new CreateMinimumRecurrenceAction)->execute($userDish, $recurrenceType, $recurrenceValue);
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidRecurrenceTypeException();
|
throw new InvalidRecurrenceTypeException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class DeleteFixedRecurrenceAction
|
||||||
public function execute(RecurrenceInterface $recurrence): void
|
public function execute(RecurrenceInterface $recurrence): void
|
||||||
{
|
{
|
||||||
if (! $recurrence instanceof WeeklyRecurrence) {
|
if (! $recurrence instanceof WeeklyRecurrence) {
|
||||||
throw new InvalidRecurrenceTypeException();
|
throw new InvalidRecurrenceTypeException;
|
||||||
}
|
}
|
||||||
|
|
||||||
$recurrence->delete();
|
$recurrence->delete();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class DeleteMinimumRecurrenceAction
|
||||||
public function execute(RecurrenceInterface $recurrence): void
|
public function execute(RecurrenceInterface $recurrence): void
|
||||||
{
|
{
|
||||||
if (! $recurrence instanceof MinimumRecurrence) {
|
if (! $recurrence instanceof MinimumRecurrence) {
|
||||||
throw new InvalidRecurrenceTypeException();
|
throw new InvalidRecurrenceTypeException;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserDishRecurrence::query()
|
UserDishRecurrence::query()
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ public function execute(UserDish $userDish, Collection $recurrences): UserDish
|
||||||
}
|
}
|
||||||
|
|
||||||
match ($recurrenceType) {
|
match ($recurrenceType) {
|
||||||
WeeklyRecurrence::class => (new CreateFixedRecurrenceAction())->execute($userDish, $recurrenceType, $recurrenceValue),
|
WeeklyRecurrence::class => (new CreateFixedRecurrenceAction)->execute($userDish, $recurrenceType, $recurrenceValue),
|
||||||
MinimumRecurrence::class => (new CreateMinimumRecurrenceAction())->execute($userDish, $recurrenceType, $recurrenceValue),
|
MinimumRecurrence::class => (new CreateMinimumRecurrenceAction)->execute($userDish, $recurrenceType, $recurrenceValue),
|
||||||
default => throw new InvalidRecurrenceTypeException(),
|
default => throw new InvalidRecurrenceTypeException,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class UpdateFixedRecurrenceAction
|
||||||
public function execute(RecurrenceInterface $recurrence, array $data): RecurrenceInterface
|
public function execute(RecurrenceInterface $recurrence, array $data): RecurrenceInterface
|
||||||
{
|
{
|
||||||
if (! $recurrence instanceof WeeklyRecurrence) {
|
if (! $recurrence instanceof WeeklyRecurrence) {
|
||||||
throw new InvalidRecurrenceTypeException();
|
throw new InvalidRecurrenceTypeException;
|
||||||
}
|
}
|
||||||
|
|
||||||
$weekday = Arr::get($data, 'recurrence_data.weekday');
|
$weekday = Arr::get($data, 'recurrence_data.weekday');
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class UpdateMinimumRecurrenceAction
|
||||||
public function execute(RecurrenceInterface $recurrence, array $data): RecurrenceInterface
|
public function execute(RecurrenceInterface $recurrence, array $data): RecurrenceInterface
|
||||||
{
|
{
|
||||||
if (! $recurrence instanceof MinimumRecurrence) {
|
if (! $recurrence instanceof MinimumRecurrence) {
|
||||||
throw new InvalidRecurrenceTypeException();
|
throw new InvalidRecurrenceTypeException;
|
||||||
}
|
}
|
||||||
|
|
||||||
$days = Arr::get($data, 'recurrence_data.days');
|
$days = Arr::get($data, 'recurrence_data.days');
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public function __invoke(Request $request)
|
||||||
$userDishes = $userDishRepository->getAllForPlanner($planner);
|
$userDishes = $userDishRepository->getAllForPlanner($planner);
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'user_dishes' => UserDishResource::collection($userDishes)->collection->toArray()
|
'user_dishes' => UserDishResource::collection($userDishes)->collection->toArray(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public function show(User $user, Dish $dish): JsonResponse
|
||||||
*/
|
*/
|
||||||
public function store(CreateUserDishRequest $request, User $user, Dish $dish): JsonResponse
|
public function store(CreateUserDishRequest $request, User $user, Dish $dish): JsonResponse
|
||||||
{
|
{
|
||||||
$userDish = (new CreateUserDishAction())->execute($dish, $user, $request->validated());
|
$userDish = (new CreateUserDishAction)->execute($dish, $user, $request->validated());
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'user_dish' => new UserDishResource($userDish),
|
'user_dish' => new UserDishResource($userDish),
|
||||||
|
|
@ -57,7 +57,7 @@ public function store(CreateUserDishRequest $request, User $user, Dish $dish): J
|
||||||
|
|
||||||
public function destroy(User $user, Dish $dish): JsonResponse
|
public function destroy(User $user, Dish $dish): JsonResponse
|
||||||
{
|
{
|
||||||
(new DeleteUserDishAction())->execute($user, $dish);
|
(new DeleteUserDishAction)->execute($user, $dish);
|
||||||
|
|
||||||
return $this->success(null);
|
return $this->success(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ public function store(StoreUserDishRecurrenceRequest $request, User $user, Dish
|
||||||
|
|
||||||
$recurrences = collect($request->validated());
|
$recurrences = collect($request->validated());
|
||||||
|
|
||||||
(new SyncRecurrencesForUserDishAction())->execute($userDish, $recurrences);
|
(new SyncRecurrencesForUserDishAction)->execute($userDish, $recurrences);
|
||||||
|
|
||||||
return $this->success([
|
return $this->success([
|
||||||
'user_dish' => new UserDishResource($userDish->refresh()),
|
'user_dish' => new UserDishResource($userDish->refresh()),
|
||||||
|
|
@ -51,9 +51,9 @@ public function update(UpdateUserDishFixedRecurrenceRequest $request, UserDish $
|
||||||
$recurrence = $recurrenceClass::findOrFail($recurrenceId);
|
$recurrence = $recurrenceClass::findOrFail($recurrenceId);
|
||||||
|
|
||||||
if ($recurrence instanceof WeeklyRecurrence) {
|
if ($recurrence instanceof WeeklyRecurrence) {
|
||||||
(new UpdateFixedRecurrenceAction())->execute($recurrence, $request->validated());
|
(new UpdateFixedRecurrenceAction)->execute($recurrence, $request->validated());
|
||||||
} elseif ($recurrenceClass === MinimumRecurrence::class) {
|
} elseif ($recurrenceClass === MinimumRecurrence::class) {
|
||||||
(new UpdateMinimumRecurrenceAction())->execute($recurrence, $request->validated());
|
(new UpdateMinimumRecurrenceAction)->execute($recurrence, $request->validated());
|
||||||
} else {
|
} else {
|
||||||
return $this->error('invalid recurrence type');
|
return $this->error('invalid recurrence type');
|
||||||
}
|
}
|
||||||
|
|
@ -72,9 +72,9 @@ public function destroy(UserDish $userDish, string $recurrenceType, int $recurre
|
||||||
$recurrence = $recurrenceClass::findOrFail($recurrenceId);
|
$recurrence = $recurrenceClass::findOrFail($recurrenceId);
|
||||||
|
|
||||||
if ($recurrence instanceof WeeklyRecurrence) {
|
if ($recurrence instanceof WeeklyRecurrence) {
|
||||||
(new DeleteFixedRecurrenceAction())->execute($recurrence);
|
(new DeleteFixedRecurrenceAction)->execute($recurrence);
|
||||||
} elseif ($recurrenceClass === MinimumRecurrence::class) {
|
} elseif ($recurrenceClass === MinimumRecurrence::class) {
|
||||||
(new DeleteMinimumRecurrenceAction())->execute($recurrence);
|
(new DeleteMinimumRecurrenceAction)->execute($recurrence);
|
||||||
} else {
|
} else {
|
||||||
return $this->error('invalid recurrence type');
|
return $this->error('invalid recurrence type');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,4 @@
|
||||||
|
|
||||||
namespace DishPlanner\UserDish\Interfaces;
|
namespace DishPlanner\UserDish\Interfaces;
|
||||||
|
|
||||||
interface FixedRecurrenceInterface
|
interface FixedRecurrenceInterface {}
|
||||||
{}
|
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,4 @@
|
||||||
|
|
||||||
namespace DishPlanner\UserDish\Interfaces;
|
namespace DishPlanner\UserDish\Interfaces;
|
||||||
|
|
||||||
interface RecurrenceInterface
|
interface RecurrenceInterface {}
|
||||||
{}
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
use App\Models\WeeklyRecurrence;
|
use App\Models\WeeklyRecurrence;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Carbon\CarbonPeriod;
|
use Carbon\CarbonPeriod;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Support\Collection as SupportCollection;
|
use Illuminate\Support\Collection as SupportCollection;
|
||||||
|
|
||||||
|
|
@ -70,7 +69,7 @@ public function findInterferingUserDishes(User $user, Carbon $date): Collection
|
||||||
->get()
|
->get()
|
||||||
->flatMap(fn (Schedule $schedule) => $schedule->scheduledUserDishes)
|
->flatMap(fn (Schedule $schedule) => $schedule->scheduledUserDishes)
|
||||||
->filter(fn (ScheduledUserDish $scheduledUserDish) => $scheduledUserDish->userDish->user_id === $user->id)
|
->filter(fn (ScheduledUserDish $scheduledUserDish) => $scheduledUserDish->userDish->user_id === $user->id)
|
||||||
->filter(fn(ScheduledUserDish $scheduledUserDish) => $scheduledUserDish->userDish->recurrences->contains('recurrence_type', MinimumRecurrence::class))
|
->filter(fn (ScheduledUserDish $scheduledUserDish) => $scheduledUserDish->userDish->recurrences->contains('recurrence_type', MinimumRecurrence::class))
|
||||||
->filter(function (ScheduledUserDish $scheduledUserDish) use ($date) {
|
->filter(function (ScheduledUserDish $scheduledUserDish) use ($date) {
|
||||||
$minimum = $scheduledUserDish->userDish
|
$minimum = $scheduledUserDish->userDish
|
||||||
->recurrences
|
->recurrences
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public function rules(): array
|
||||||
MinimumRecurrence::class,
|
MinimumRecurrence::class,
|
||||||
WeeklyRecurrence::class,
|
WeeklyRecurrence::class,
|
||||||
]),
|
]),
|
||||||
'required_with:*.recurrence_value'
|
'required_with:*.recurrence_value',
|
||||||
],
|
],
|
||||||
'*.value' => ['sometimes', 'integer', 'required_with:*.recurrence_type'],
|
'*.value' => ['sometimes', 'integer', 'required_with:*.recurrence_type'],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@ public function rules(): array
|
||||||
'recurrence_type' => [
|
'recurrence_type' => [
|
||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
'in:' . implode(',', [
|
'in:'.implode(',', [
|
||||||
MinimumRecurrence::class,
|
MinimumRecurrence::class,
|
||||||
WeeklyRecurrence::class,
|
WeeklyRecurrence::class,
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
'recurrence_data' => 'required|array',
|
'recurrence_data' => 'required|array',
|
||||||
'recurrence_data.days' => 'required_if:recurrence_type,' . MinimumRecurrence::class . '|integer|min:1',
|
'recurrence_data.days' => 'required_if:recurrence_type,'.MinimumRecurrence::class.'|integer|min:1',
|
||||||
'recurrence_data.weekday' => 'required_if:recurrence_type,' . WeeklyRecurrence::class . '|integer|between:0,6',
|
'recurrence_data.weekday' => 'required_if:recurrence_type,'.WeeklyRecurrence::class.'|integer|between:0,6',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@
|
||||||
|
|
||||||
namespace Tests\Browser\Auth;
|
namespace Tests\Browser\Auth;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use App\Models\Planner;
|
use App\Models\Planner;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class LoginTest extends DuskTestCase
|
class LoginTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
protected static $testPlanner = null;
|
protected static $testPlanner = null;
|
||||||
|
|
||||||
protected static $testEmail = null;
|
protected static $testEmail = null;
|
||||||
|
|
||||||
protected static $testPassword = 'password';
|
protected static $testPassword = 'password';
|
||||||
|
|
||||||
protected function ensureTestPlannerExists(): void
|
protected function ensureTestPlannerExists(): void
|
||||||
|
|
@ -26,49 +28,49 @@ protected function ensureTestPlannerExists(): void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSuccessfulLogin(): void
|
public function test_successful_login(): void
|
||||||
{
|
{
|
||||||
$this->ensureTestPlannerExists();
|
$this->ensureTestPlannerExists();
|
||||||
|
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$browser->driver->manage()->deleteAllCookies();
|
$browser->driver->manage()->deleteAllCookies();
|
||||||
$browser->visit('http://dishplanner_app:8000/login')
|
$browser->visit('http://dishplanner_app:8000/login')
|
||||||
->waitFor('input[id="email"]', self::TIMEOUT_SHORT)
|
->waitFor('input[id="email"]', self::TIMEOUT_SHORT)
|
||||||
->clear('input[id="email"]')
|
->clear('input[id="email"]')
|
||||||
->type('input[id="email"]', self::$testEmail)
|
->type('input[id="email"]', self::$testEmail)
|
||||||
->clear('input[id="password"]')
|
->clear('input[id="password"]')
|
||||||
->type('input[id="password"]', self::$testPassword)
|
->type('input[id="password"]', self::$testPassword)
|
||||||
->press('Login')
|
->press('Login')
|
||||||
->waitForLocation('/dashboard', self::TIMEOUT_MEDIUM)
|
->waitForLocation('/dashboard', self::TIMEOUT_MEDIUM)
|
||||||
->assertPathIs('/dashboard');
|
->assertPathIs('/dashboard');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoginWithWrongCredentials(): void
|
public function test_login_with_wrong_credentials(): void
|
||||||
{
|
{
|
||||||
$this->ensureTestPlannerExists();
|
$this->ensureTestPlannerExists();
|
||||||
|
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$browser->driver->manage()->deleteAllCookies();
|
$browser->driver->manage()->deleteAllCookies();
|
||||||
$browser->visit('http://dishplanner_app:8000/login')
|
$browser->visit('http://dishplanner_app:8000/login')
|
||||||
->waitFor('input[id="email"]', self::TIMEOUT_SHORT)
|
->waitFor('input[id="email"]', self::TIMEOUT_SHORT)
|
||||||
->clear('input[id="email"]')
|
->clear('input[id="email"]')
|
||||||
->type('input[id="email"]', self::$testEmail)
|
->type('input[id="email"]', self::$testEmail)
|
||||||
->clear('input[id="password"]')
|
->clear('input[id="password"]')
|
||||||
->type('input[id="password"]', 'wrongpassword')
|
->type('input[id="password"]', 'wrongpassword')
|
||||||
->press('Login')
|
->press('Login')
|
||||||
->pause(self::PAUSE_MEDIUM)
|
->pause(self::PAUSE_MEDIUM)
|
||||||
->assertPathIs('/login')
|
->assertPathIs('/login')
|
||||||
->assertSee('These credentials do not match our records');
|
->assertSee('These credentials do not match our records');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLoginFormRequiredFields(): void
|
public function test_login_form_required_fields(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$browser->driver->manage()->deleteAllCookies();
|
$browser->driver->manage()->deleteAllCookies();
|
||||||
$browser->visit('http://dishplanner_app:8000/login')
|
$browser->visit('http://dishplanner_app:8000/login')
|
||||||
->waitFor('input[id="email"]', self::TIMEOUT_SHORT);
|
->waitFor('input[id="email"]', self::TIMEOUT_SHORT);
|
||||||
|
|
||||||
// Check that both fields have the required attribute
|
// Check that both fields have the required attribute
|
||||||
$browser->assertAttribute('input[id="email"]', 'required', 'true');
|
$browser->assertAttribute('input[id="email"]', 'required', 'true');
|
||||||
|
|
@ -82,8 +84,8 @@ public function testLoginFormRequiredFields(): void
|
||||||
|
|
||||||
// Test that we stay on login page if we try to submit with empty fields
|
// Test that we stay on login page if we try to submit with empty fields
|
||||||
$browser->press('Login')
|
$browser->press('Login')
|
||||||
->pause(self::PAUSE_SHORT)
|
->pause(self::PAUSE_SHORT)
|
||||||
->assertPathIs('/login');
|
->assertPathIs('/login');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public function selector(): string
|
||||||
public function assert(Browser $browser): void
|
public function assert(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertVisible($this->selector());
|
$browser->assertVisible($this->selector());
|
||||||
|
|
||||||
if ($this->mode === 'create') {
|
if ($this->mode === 'create') {
|
||||||
$browser->assertSee('Add New Dish');
|
$browser->assertSee('Add New Dish');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -60,12 +60,12 @@ public function elements(): array
|
||||||
public function fillForm(Browser $browser, string $name, ?string $description = null): void
|
public function fillForm(Browser $browser, string $name, ?string $description = null): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@name-input')
|
$browser->waitFor('@name-input')
|
||||||
->clear('@name-input')
|
->clear('@name-input')
|
||||||
->type('@name-input', $name);
|
->type('@name-input', $name);
|
||||||
|
|
||||||
if ($description !== null && $browser->element('@description-input')) {
|
if ($description !== null && $browser->element('@description-input')) {
|
||||||
$browser->clear('@description-input')
|
$browser->clear('@description-input')
|
||||||
->type('@description-input', $description);
|
->type('@description-input', $description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,4 +102,4 @@ public function assertValidationError(Browser $browser, string $message = 'requi
|
||||||
{
|
{
|
||||||
$browser->assertSee($message);
|
$browser->assertSee($message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ public function selector(): string
|
||||||
public function assert(Browser $browser): void
|
public function assert(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertVisible($this->selector())
|
$browser->assertVisible($this->selector())
|
||||||
->assertVisible('@email')
|
->assertVisible('@email')
|
||||||
->assertVisible('@password')
|
->assertVisible('@password')
|
||||||
->assertVisible('@submit');
|
->assertVisible('@submit');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,7 +48,7 @@ public function elements(): array
|
||||||
public function fillForm(Browser $browser, string $email, string $password): void
|
public function fillForm(Browser $browser, string $email, string $password): void
|
||||||
{
|
{
|
||||||
$browser->type('@email', $email)
|
$browser->type('@email', $email)
|
||||||
->type('@password', $password);
|
->type('@password', $password);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,9 +74,9 @@ public function loginWith(Browser $browser, string $email, string $password): vo
|
||||||
public function assertFieldsRequired(Browser $browser): void
|
public function assertFieldsRequired(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertAttribute('@email', 'required', 'true')
|
$browser->assertAttribute('@email', 'required', 'true')
|
||||||
->assertAttribute('@password', 'required', 'true')
|
->assertAttribute('@password', 'required', 'true')
|
||||||
->assertAttribute('@email', 'type', 'email')
|
->assertAttribute('@email', 'type', 'email')
|
||||||
->assertAttribute('@password', 'type', 'password');
|
->assertAttribute('@password', 'type', 'password');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -86,4 +86,4 @@ public function assertHasErrors(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertPresent('@error');
|
$browser->assertPresent('@error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,19 @@
|
||||||
namespace Tests\Browser\Dishes;
|
namespace Tests\Browser\Dishes;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\Pages\DishesPage;
|
|
||||||
use Tests\Browser\Components\DishModal;
|
use Tests\Browser\Components\DishModal;
|
||||||
use Tests\Browser\LoginHelpers;
|
use Tests\Browser\LoginHelpers;
|
||||||
|
use Tests\Browser\Pages\DishesPage;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class CreateDishFormValidationTest extends DuskTestCase
|
class CreateDishFormValidationTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
use LoginHelpers;
|
use LoginHelpers;
|
||||||
|
|
||||||
protected static $createDishFormValidationTestPlanner = null;
|
protected static $createDishFormValidationTestPlanner = null;
|
||||||
|
|
||||||
protected static $createDishFormValidationTestEmail = null;
|
protected static $createDishFormValidationTestEmail = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
@ -22,7 +23,7 @@ protected function setUp(): void
|
||||||
self::$testPlanner = self::$createDishFormValidationTestPlanner;
|
self::$testPlanner = self::$createDishFormValidationTestPlanner;
|
||||||
self::$testEmail = self::$createDishFormValidationTestEmail;
|
self::$testEmail = self::$createDishFormValidationTestEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
// Save the planner for next test method in this class
|
// Save the planner for next test method in this class
|
||||||
|
|
@ -31,19 +32,19 @@ protected function tearDown(): void
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateDishFormValidation(): void
|
public function test_create_dish_form_validation(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToDishes($browser);
|
$this->loginAndGoToDishes($browser);
|
||||||
|
|
||||||
$browser->on(new DishesPage)
|
$browser->on(new DishesPage)
|
||||||
->openCreateModal()
|
->openCreateModal()
|
||||||
->within(new DishModal('create'), function ($browser) {
|
->within(new DishModal('create'), function ($browser) {
|
||||||
$browser->fillForm('', null)
|
$browser->fillForm('', null)
|
||||||
->submit()
|
->submit()
|
||||||
->pause(2000)
|
->pause(2000)
|
||||||
->assertValidationError('required');
|
->assertValidationError('required');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,19 @@
|
||||||
namespace Tests\Browser\Dishes;
|
namespace Tests\Browser\Dishes;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\Pages\DishesPage;
|
|
||||||
use Tests\Browser\Components\DishModal;
|
use Tests\Browser\Components\DishModal;
|
||||||
use Tests\Browser\LoginHelpers;
|
use Tests\Browser\LoginHelpers;
|
||||||
|
use Tests\Browser\Pages\DishesPage;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class CreateDishSuccessTest extends DuskTestCase
|
class CreateDishSuccessTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
use LoginHelpers;
|
use LoginHelpers;
|
||||||
|
|
||||||
protected static $createDishSuccessTestPlanner = null;
|
protected static $createDishSuccessTestPlanner = null;
|
||||||
|
|
||||||
protected static $createDishSuccessTestEmail = null;
|
protected static $createDishSuccessTestEmail = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
@ -22,7 +23,7 @@ protected function setUp(): void
|
||||||
self::$testPlanner = self::$createDishSuccessTestPlanner;
|
self::$testPlanner = self::$createDishSuccessTestPlanner;
|
||||||
self::$testEmail = self::$createDishSuccessTestEmail;
|
self::$testEmail = self::$createDishSuccessTestEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
// Save the planner for next test method in this class
|
// Save the planner for next test method in this class
|
||||||
|
|
@ -31,22 +32,22 @@ protected function tearDown(): void
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanCreateDishSuccessfully(): void
|
public function test_can_create_dish_successfully(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$dishName = 'Test Dish ' . uniqid();
|
$dishName = 'Test Dish '.uniqid();
|
||||||
|
|
||||||
$this->loginAndGoToDishes($browser);
|
$this->loginAndGoToDishes($browser);
|
||||||
|
|
||||||
$browser->on(new DishesPage)
|
$browser->on(new DishesPage)
|
||||||
->openCreateModal()
|
->openCreateModal()
|
||||||
->within(new DishModal('create'), function ($browser) use ($dishName) {
|
->within(new DishModal('create'), function ($browser) use ($dishName) {
|
||||||
$browser->fillForm($dishName)
|
$browser->fillForm($dishName)
|
||||||
->submit();
|
->submit();
|
||||||
})
|
})
|
||||||
->pause(3000)
|
->pause(3000)
|
||||||
->assertDishVisible($dishName)
|
->assertDishVisible($dishName)
|
||||||
->assertSee('Dish created successfully');
|
->assertSee('Dish created successfully');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,18 @@
|
||||||
namespace Tests\Browser\Dishes;
|
namespace Tests\Browser\Dishes;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\Pages\DishesPage;
|
|
||||||
use Tests\Browser\Components\DishModal;
|
|
||||||
use Tests\Browser\LoginHelpers;
|
use Tests\Browser\LoginHelpers;
|
||||||
|
use Tests\Browser\Pages\DishesPage;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class CreateDishTest extends DuskTestCase
|
class CreateDishTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
use LoginHelpers;
|
use LoginHelpers;
|
||||||
|
|
||||||
protected static $createDishTestPlanner = null;
|
protected static $createDishTestPlanner = null;
|
||||||
|
|
||||||
protected static $createDishTestEmail = null;
|
protected static $createDishTestEmail = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
@ -22,7 +22,7 @@ protected function setUp(): void
|
||||||
self::$testPlanner = self::$createDishTestPlanner;
|
self::$testPlanner = self::$createDishTestPlanner;
|
||||||
self::$testEmail = self::$createDishTestEmail;
|
self::$testEmail = self::$createDishTestEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
// Save the planner for next test method in this class
|
// Save the planner for next test method in this class
|
||||||
|
|
@ -30,18 +30,18 @@ protected function tearDown(): void
|
||||||
self::$createDishTestEmail = self::$testEmail;
|
self::$createDishTestEmail = self::$testEmail;
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanAccessDishesPage(): void
|
public function test_can_access_dishes_page(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToDishes($browser);
|
$this->loginAndGoToDishes($browser);
|
||||||
|
|
||||||
$browser->on(new DishesPage)
|
$browser->on(new DishesPage)
|
||||||
->assertSee('MANAGE DISHES')
|
->assertSee('MANAGE DISHES')
|
||||||
->assertSee('Add Dish');
|
->assertSee('Add Dish');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Moved to separate single-method test files to avoid static planner issues
|
// TODO: Moved to separate single-method test files to avoid static planner issues
|
||||||
// See: OpenCreateDishModalTest, CreateDishFormValidationTest, CancelDishCreationTest, CreateDishSuccessTest
|
// See: OpenCreateDishModalTest, CreateDishFormValidationTest, CancelDishCreationTest, CreateDishSuccessTest
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,19 @@
|
||||||
|
|
||||||
namespace Tests\Browser\Dishes;
|
namespace Tests\Browser\Dishes;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\LoginHelpers;
|
|
||||||
use App\Models\Planner;
|
use App\Models\Planner;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\Browser\LoginHelpers;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class DeleteDishTest extends DuskTestCase
|
class DeleteDishTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
use LoginHelpers;
|
use LoginHelpers;
|
||||||
|
|
||||||
protected static $deleteDishTestPlanner = null;
|
protected static $deleteDishTestPlanner = null;
|
||||||
|
|
||||||
protected static $deleteDishTestEmail = null;
|
protected static $deleteDishTestEmail = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
@ -21,7 +22,7 @@ protected function setUp(): void
|
||||||
self::$testPlanner = self::$deleteDishTestPlanner;
|
self::$testPlanner = self::$deleteDishTestPlanner;
|
||||||
self::$testEmail = self::$deleteDishTestEmail;
|
self::$testEmail = self::$deleteDishTestEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
// Save the planner for next test method in this class
|
// Save the planner for next test method in this class
|
||||||
|
|
@ -29,14 +30,14 @@ protected function tearDown(): void
|
||||||
self::$deleteDishTestEmail = self::$testEmail;
|
self::$deleteDishTestEmail = self::$testEmail;
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanAccessDeleteFeature(): void
|
public function test_can_access_delete_feature(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToDishes($browser)
|
$this->loginAndGoToDishes($browser)
|
||||||
->assertPathIs('/dishes')
|
->assertPathIs('/dishes')
|
||||||
->assertSee('MANAGE DISHES');
|
->assertSee('MANAGE DISHES');
|
||||||
|
|
||||||
// Verify that delete functionality is available by looking for the text in the page source
|
// Verify that delete functionality is available by looking for the text in the page source
|
||||||
$pageSource = $browser->driver->getPageSource();
|
$pageSource = $browser->driver->getPageSource();
|
||||||
$this->assertStringContainsString('Delete', $pageSource);
|
$this->assertStringContainsString('Delete', $pageSource);
|
||||||
|
|
@ -59,7 +60,7 @@ public function testDeletionSafetyFeatures(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToDishes($browser);
|
$this->loginAndGoToDishes($browser);
|
||||||
|
|
||||||
// Check that Livewire component includes all CRUD features
|
// Check that Livewire component includes all CRUD features
|
||||||
$pageSource = $browser->driver->getPageSource();
|
$pageSource = $browser->driver->getPageSource();
|
||||||
$this->assertStringContainsString('MANAGE DISHES', $pageSource);
|
$this->assertStringContainsString('MANAGE DISHES', $pageSource);
|
||||||
|
|
@ -73,4 +74,4 @@ public function testDeletionSafetyFeatures(): void
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@
|
||||||
namespace Tests\Browser\Dishes;
|
namespace Tests\Browser\Dishes;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\LoginHelpers;
|
use Tests\Browser\LoginHelpers;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class DishDeletionSafetyTest extends DuskTestCase
|
class DishDeletionSafetyTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
use LoginHelpers;
|
use LoginHelpers;
|
||||||
|
|
||||||
protected static $dishDeletionSafetyTestPlanner = null;
|
protected static $dishDeletionSafetyTestPlanner = null;
|
||||||
|
|
||||||
protected static $dishDeletionSafetyTestEmail = null;
|
protected static $dishDeletionSafetyTestEmail = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
@ -20,7 +21,7 @@ protected function setUp(): void
|
||||||
self::$testPlanner = self::$dishDeletionSafetyTestPlanner;
|
self::$testPlanner = self::$dishDeletionSafetyTestPlanner;
|
||||||
self::$testEmail = self::$dishDeletionSafetyTestEmail;
|
self::$testEmail = self::$dishDeletionSafetyTestEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
// Save the planner for next test method in this class
|
// Save the planner for next test method in this class
|
||||||
|
|
@ -29,11 +30,11 @@ protected function tearDown(): void
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDeletionSafetyFeatures(): void
|
public function test_deletion_safety_features(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToDishes($browser);
|
$this->loginAndGoToDishes($browser);
|
||||||
|
|
||||||
// Check that Livewire component includes all CRUD features
|
// Check that Livewire component includes all CRUD features
|
||||||
$pageSource = $browser->driver->getPageSource();
|
$pageSource = $browser->driver->getPageSource();
|
||||||
$this->assertStringContainsString('MANAGE DISHES', $pageSource);
|
$this->assertStringContainsString('MANAGE DISHES', $pageSource);
|
||||||
|
|
@ -46,4 +47,4 @@ public function testDeletionSafetyFeatures(): void
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,19 @@
|
||||||
|
|
||||||
namespace Tests\Browser\Dishes;
|
namespace Tests\Browser\Dishes;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\LoginHelpers;
|
|
||||||
use App\Models\Planner;
|
use App\Models\Planner;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\Browser\LoginHelpers;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class EditDishTest extends DuskTestCase
|
class EditDishTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
use LoginHelpers;
|
use LoginHelpers;
|
||||||
|
|
||||||
protected static $editDishTestPlanner = null;
|
protected static $editDishTestPlanner = null;
|
||||||
|
|
||||||
protected static $editDishTestEmail = null;
|
protected static $editDishTestEmail = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
@ -21,7 +22,7 @@ protected function setUp(): void
|
||||||
self::$testPlanner = self::$editDishTestPlanner;
|
self::$testPlanner = self::$editDishTestPlanner;
|
||||||
self::$testEmail = self::$editDishTestEmail;
|
self::$testEmail = self::$editDishTestEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
// Save the planner for next test method in this class
|
// Save the planner for next test method in this class
|
||||||
|
|
@ -29,36 +30,36 @@ protected function tearDown(): void
|
||||||
self::$editDishTestEmail = self::$testEmail;
|
self::$editDishTestEmail = self::$testEmail;
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanAccessEditFeature(): void
|
public function test_can_access_edit_feature(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToDishes($browser)
|
$this->loginAndGoToDishes($browser)
|
||||||
->assertPathIs('/dishes')
|
->assertPathIs('/dishes')
|
||||||
->assertSee('MANAGE DISHES');
|
->assertSee('MANAGE DISHES');
|
||||||
|
|
||||||
// Verify that edit functionality is available by looking for the text in the page source
|
// Verify that edit functionality is available by looking for the text in the page source
|
||||||
$pageSource = $browser->driver->getPageSource();
|
$pageSource = $browser->driver->getPageSource();
|
||||||
$this->assertStringContainsString('Edit', $pageSource);
|
$this->assertStringContainsString('Edit', $pageSource);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testEditModalComponents(): void
|
public function test_edit_modal_components(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToDishes($browser)
|
$this->loginAndGoToDishes($browser)
|
||||||
->assertSee('MANAGE DISHES')
|
->assertSee('MANAGE DISHES')
|
||||||
->assertSee('Add Dish');
|
->assertSee('Add Dish');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDishesPageStructure(): void
|
public function test_dishes_page_structure(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToDishes($browser)
|
$this->loginAndGoToDishes($browser)
|
||||||
->assertSee('MANAGE DISHES')
|
->assertSee('MANAGE DISHES')
|
||||||
->assertSee('Add Dish');
|
->assertSee('Add Dish');
|
||||||
|
|
||||||
// Check that the dishes CRUD structure is present
|
// Check that the dishes CRUD structure is present
|
||||||
$pageSource = $browser->driver->getPageSource();
|
$pageSource = $browser->driver->getPageSource();
|
||||||
// Either we have dishes with Edit/Delete buttons OR "No dishes found" message
|
// Either we have dishes with Edit/Delete buttons OR "No dishes found" message
|
||||||
|
|
@ -70,4 +71,4 @@ public function testDishesPageStructure(): void
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,29 @@
|
||||||
|
|
||||||
namespace Tests\Browser;
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use App\Models\Planner;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
trait LoginHelpers
|
trait LoginHelpers
|
||||||
{
|
{
|
||||||
protected static $testPlanner = null;
|
protected static $testPlanner = null;
|
||||||
|
|
||||||
protected static $testEmail = null;
|
protected static $testEmail = null;
|
||||||
|
|
||||||
protected static $testPassword = 'password';
|
protected static $testPassword = 'password';
|
||||||
|
|
||||||
protected function ensureTestPlannerExists(): void
|
protected function ensureTestPlannerExists(): void
|
||||||
{
|
{
|
||||||
// Always create a fresh planner for each test class to avoid session conflicts
|
// Always create a fresh planner for each test class to avoid session conflicts
|
||||||
if (self::$testPlanner === null || !self::$testPlanner->exists) {
|
if (self::$testPlanner === null || ! self::$testPlanner->exists) {
|
||||||
// Generate unique email for this test run
|
// Generate unique email for this test run
|
||||||
self::$testEmail = fake()->unique()->safeEmail();
|
self::$testEmail = fake()->unique()->safeEmail();
|
||||||
|
|
||||||
self::$testPlanner = \App\Models\Planner::factory()->create([
|
self::$testPlanner = Planner::factory()->create([
|
||||||
'email' => self::$testEmail,
|
'email' => self::$testEmail,
|
||||||
'password' => \Illuminate\Support\Facades\Hash::make(self::$testPassword),
|
'password' => Hash::make(self::$testPassword),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,21 +32,21 @@ protected function ensureTestPlannerExists(): void
|
||||||
protected function loginAndNavigate(Browser $browser, string $page = '/dashboard'): Browser
|
protected function loginAndNavigate(Browser $browser, string $page = '/dashboard'): Browser
|
||||||
{
|
{
|
||||||
$this->ensureTestPlannerExists();
|
$this->ensureTestPlannerExists();
|
||||||
|
|
||||||
// Clear browser session and cookies to start fresh
|
// Clear browser session and cookies to start fresh
|
||||||
$browser->driver->manage()->deleteAllCookies();
|
$browser->driver->manage()->deleteAllCookies();
|
||||||
|
|
||||||
return $browser->visit('http://dishplanner_app:8000/login')
|
return $browser->visit('http://dishplanner_app:8000/login')
|
||||||
->waitFor('input[id="email"]', DuskTestCase::TIMEOUT_SHORT)
|
->waitFor('input[id="email"]', DuskTestCase::TIMEOUT_SHORT)
|
||||||
->clear('input[id="email"]')
|
->clear('input[id="email"]')
|
||||||
->type('input[id="email"]', self::$testEmail)
|
->type('input[id="email"]', self::$testEmail)
|
||||||
->clear('input[id="password"]')
|
->clear('input[id="password"]')
|
||||||
->type('input[id="password"]', self::$testPassword)
|
->type('input[id="password"]', self::$testPassword)
|
||||||
->press('Sign In')
|
->press('Sign In')
|
||||||
->waitForLocation('/dashboard', DuskTestCase::TIMEOUT_MEDIUM) // Wait for successful login redirect
|
->waitForLocation('/dashboard', DuskTestCase::TIMEOUT_MEDIUM) // Wait for successful login redirect
|
||||||
->pause(DuskTestCase::PAUSE_SHORT) // Brief pause for any initialization
|
->pause(DuskTestCase::PAUSE_SHORT) // Brief pause for any initialization
|
||||||
->visit('http://dishplanner_app:8000' . $page)
|
->visit('http://dishplanner_app:8000'.$page)
|
||||||
->pause(DuskTestCase::PAUSE_MEDIUM); // Let Livewire components initialize
|
->pause(DuskTestCase::PAUSE_MEDIUM); // Let Livewire components initialize
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loginAndGoToDishes(Browser $browser): Browser
|
protected function loginAndGoToDishes(Browser $browser): Browser
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public function url(): string
|
||||||
public function assert(Browser $browser): void
|
public function assert(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertPathIs($this->url())
|
$browser->assertPathIs($this->url())
|
||||||
->assertSee('MANAGE DISHES');
|
->assertSee('MANAGE DISHES');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,8 +44,8 @@ public function elements(): array
|
||||||
public function openCreateModal(Browser $browser): void
|
public function openCreateModal(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@add-button')
|
$browser->waitFor('@add-button')
|
||||||
->click('@add-button')
|
->click('@add-button')
|
||||||
->pause(1000);
|
->pause(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -83,4 +83,4 @@ public function assertNoDishes(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertSee('No dishes found');
|
$browser->assertSee('No dishes found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ public function url(): string
|
||||||
public function assert(Browser $browser): void
|
public function assert(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertPathIs($this->url())
|
$browser->assertPathIs($this->url())
|
||||||
->assertSee('Login')
|
->assertSee('Login')
|
||||||
->assertPresent((new LoginForm)->selector());
|
->assertPresent((new LoginForm)->selector());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,4 +44,4 @@ public function goToRegistration(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->click('@register-link');
|
$browser->click('@register-link');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,4 @@ public static function siteElements(): array
|
||||||
'@alert' => '[role="alert"]',
|
'@alert' => '[role="alert"]',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public function url(): string
|
||||||
public function assert(Browser $browser): void
|
public function assert(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertPathIs($this->url())
|
$browser->assertPathIs($this->url())
|
||||||
->assertSee('SCHEDULE');
|
->assertSee('SCHEDULE');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function elements(): array
|
public function elements(): array
|
||||||
|
|
@ -34,49 +34,49 @@ public function elements(): array
|
||||||
public function clickGenerate(Browser $browser): void
|
public function clickGenerate(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@generate-button')
|
$browser->waitFor('@generate-button')
|
||||||
->click('@generate-button')
|
->click('@generate-button')
|
||||||
->pause(2000); // Wait for generation
|
->pause(2000); // Wait for generation
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clickClearMonth(Browser $browser): void
|
public function clickClearMonth(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@clear-month-button')
|
$browser->waitFor('@clear-month-button')
|
||||||
->click('@clear-month-button')
|
->click('@clear-month-button')
|
||||||
->pause(1000);
|
->pause(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function goToPreviousMonth(Browser $browser): void
|
public function goToPreviousMonth(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@previous-month')
|
$browser->waitFor('@previous-month')
|
||||||
->click('@previous-month')
|
->click('@previous-month')
|
||||||
->pause(500);
|
->pause(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function goToNextMonth(Browser $browser): void
|
public function goToNextMonth(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@next-month')
|
$browser->waitFor('@next-month')
|
||||||
->click('@next-month')
|
->click('@next-month')
|
||||||
->pause(500);
|
->pause(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectMonth(Browser $browser, int $month): void
|
public function selectMonth(Browser $browser, int $month): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@month-select')
|
$browser->waitFor('@month-select')
|
||||||
->select('@month-select', $month)
|
->select('@month-select', $month)
|
||||||
->pause(500);
|
->pause(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectYear(Browser $browser, int $year): void
|
public function selectYear(Browser $browser, int $year): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@year-select')
|
$browser->waitFor('@year-select')
|
||||||
->select('@year-select', $year)
|
->select('@year-select', $year)
|
||||||
->pause(500);
|
->pause(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toggleClearExisting(Browser $browser): void
|
public function toggleClearExisting(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@clear-existing-checkbox')
|
$browser->waitFor('@clear-existing-checkbox')
|
||||||
->click('@clear-existing-checkbox');
|
->click('@clear-existing-checkbox');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectUser(Browser $browser, string $userName): void
|
public function selectUser(Browser $browser, string $userName): void
|
||||||
|
|
@ -84,7 +84,7 @@ public function selectUser(Browser $browser, string $userName): void
|
||||||
$browser->check("input[type='checkbox'][value]", $userName);
|
$browser->check("input[type='checkbox'][value]", $userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assertSuccessMessage(Browser $browser, string $message = null): void
|
public function assertSuccessMessage(Browser $browser, ?string $message = null): void
|
||||||
{
|
{
|
||||||
if ($message) {
|
if ($message) {
|
||||||
$browser->assertSee($message);
|
$browser->assertSee($message);
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public function url(): string
|
||||||
public function assert(Browser $browser): void
|
public function assert(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->assertPathIs($this->url())
|
$browser->assertPathIs($this->url())
|
||||||
->assertSee('MANAGE USERS');
|
->assertSee('MANAGE USERS');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -43,8 +43,8 @@ public function elements(): array
|
||||||
public function openCreateModal(Browser $browser): void
|
public function openCreateModal(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('@add-button')
|
$browser->waitFor('@add-button')
|
||||||
->click('@add-button')
|
->click('@add-button')
|
||||||
->pause(1000);
|
->pause(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -63,8 +63,8 @@ public function clickDeleteForUser(Browser $browser, string $userName): void
|
||||||
public function clickFirstDeleteButton(Browser $browser): void
|
public function clickFirstDeleteButton(Browser $browser): void
|
||||||
{
|
{
|
||||||
$browser->waitFor('button.bg-danger', 5)
|
$browser->waitFor('button.bg-danger', 5)
|
||||||
->click('button.bg-danger')
|
->click('button.bg-danger')
|
||||||
->pause(1000);
|
->pause(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,4 +90,4 @@ public function assertSuccessMessage(Browser $browser, string $message): void
|
||||||
{
|
{
|
||||||
$browser->assertSee($message);
|
$browser->assertSee($message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace Tests\Browser;
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
use Tests\DuskTestCase;
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
|
|
||||||
class RedirectTest extends DuskTestCase
|
class RedirectTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
|
|
@ -13,26 +13,26 @@ class RedirectTest extends DuskTestCase
|
||||||
/**
|
/**
|
||||||
* Test that unauthenticated users are redirected to login
|
* Test that unauthenticated users are redirected to login
|
||||||
*/
|
*/
|
||||||
public function testUnauthenticatedRedirectsToLogin()
|
public function test_unauthenticated_redirects_to_login()
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$browser->visit('http://dishplanner_app:8000/dashboard')
|
$browser->visit('http://dishplanner_app:8000/dashboard')
|
||||||
->assertPathIs('/login')
|
->assertPathIs('/login')
|
||||||
->assertSee('Login');
|
->assertSee('Login');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that login page loads correctly
|
* Test that login page loads correctly
|
||||||
*/
|
*/
|
||||||
public function testLoginPageLoads()
|
public function test_login_page_loads()
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$browser->visit('http://dishplanner_app:8000/login')
|
$browser->visit('http://dishplanner_app:8000/login')
|
||||||
->assertPathIs('/login')
|
->assertPathIs('/login')
|
||||||
->assertSee('Login')
|
->assertSee('Login')
|
||||||
->assertSee('Email')
|
->assertSee('Email')
|
||||||
->assertSee('Password');
|
->assertSee('Password');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,19 @@
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\Pages\SchedulePage;
|
use Tests\Browser\Pages\SchedulePage;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class GenerateScheduleTest extends DuskTestCase
|
class GenerateScheduleTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
protected static $planner = null;
|
protected static $planner = null;
|
||||||
|
|
||||||
protected static $email = null;
|
protected static $email = null;
|
||||||
|
|
||||||
protected static $password = 'password';
|
protected static $password = 'password';
|
||||||
|
|
||||||
protected static $user = null;
|
protected static $user = null;
|
||||||
|
|
||||||
protected static $dish = null;
|
protected static $dish = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
|
|
@ -52,73 +56,73 @@ protected function loginAsPlanner(Browser $browser): Browser
|
||||||
$browser->driver->manage()->deleteAllCookies();
|
$browser->driver->manage()->deleteAllCookies();
|
||||||
|
|
||||||
return $browser->visit('http://dishplanner_app:8000/login')
|
return $browser->visit('http://dishplanner_app:8000/login')
|
||||||
->waitFor('input[id="email"]', DuskTestCase::TIMEOUT_SHORT)
|
->waitFor('input[id="email"]', DuskTestCase::TIMEOUT_SHORT)
|
||||||
->clear('input[id="email"]')
|
->clear('input[id="email"]')
|
||||||
->type('input[id="email"]', self::$email)
|
->type('input[id="email"]', self::$email)
|
||||||
->clear('input[id="password"]')
|
->clear('input[id="password"]')
|
||||||
->type('input[id="password"]', self::$password)
|
->type('input[id="password"]', self::$password)
|
||||||
->press('Login')
|
->press('Login')
|
||||||
->waitForLocation('/dashboard', DuskTestCase::TIMEOUT_MEDIUM)
|
->waitForLocation('/dashboard', DuskTestCase::TIMEOUT_MEDIUM)
|
||||||
->pause(DuskTestCase::PAUSE_SHORT)
|
->pause(DuskTestCase::PAUSE_SHORT)
|
||||||
->visit('http://dishplanner_app:8000/schedule')
|
->visit('http://dishplanner_app:8000/schedule')
|
||||||
->pause(DuskTestCase::PAUSE_MEDIUM);
|
->pause(DuskTestCase::PAUSE_MEDIUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanGenerateScheduleWithUserAndDish(): void
|
public function test_can_generate_schedule_with_user_and_dish(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAsPlanner($browser);
|
$this->loginAsPlanner($browser);
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
->assertSee('Test User') // User should be in selection
|
->assertSee('Test User') // User should be in selection
|
||||||
->clickGenerate()
|
->clickGenerate()
|
||||||
->pause(2000)
|
->pause(2000)
|
||||||
// Verify schedule was generated by checking dish appears on calendar
|
// Verify schedule was generated by checking dish appears on calendar
|
||||||
->assertSee('Test Dish');
|
->assertSee('Test Dish');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGeneratedScheduleShowsDishOnCalendar(): void
|
public function test_generated_schedule_shows_dish_on_calendar(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAsPlanner($browser);
|
$this->loginAsPlanner($browser);
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
->clickGenerate()
|
->clickGenerate()
|
||||||
->pause(2000)
|
->pause(2000)
|
||||||
// The dish should appear somewhere on the calendar
|
// The dish should appear somewhere on the calendar
|
||||||
->assertSee('Test Dish');
|
->assertSee('Test Dish');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanClearMonthSchedule(): void
|
public function test_can_clear_month_schedule(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAsPlanner($browser);
|
$this->loginAsPlanner($browser);
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
// First generate a schedule
|
// First generate a schedule
|
||||||
->clickGenerate()
|
->clickGenerate()
|
||||||
->pause(2000)
|
->pause(2000)
|
||||||
->assertSee('Test Dish') // Verify generated
|
->assertSee('Test Dish') // Verify generated
|
||||||
// Then clear it
|
// Then clear it
|
||||||
->clickClearMonth()
|
->clickClearMonth()
|
||||||
->pause(1000)
|
->pause(1000)
|
||||||
// After clearing, should see "No dishes scheduled" on calendar days
|
// After clearing, should see "No dishes scheduled" on calendar days
|
||||||
->assertSee('No dishes scheduled');
|
->assertSee('No dishes scheduled');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUserSelectionAffectsGeneration(): void
|
public function test_user_selection_affects_generation(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAsPlanner($browser);
|
$this->loginAsPlanner($browser);
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
// Verify the user checkbox is present
|
// Verify the user checkbox is present
|
||||||
->assertSee('Test User')
|
->assertSee('Test User')
|
||||||
// User should be selected by default
|
// User should be selected by default
|
||||||
->assertChecked("input[value='" . self::$user->id . "']");
|
->assertChecked("input[value='".self::$user->id."']");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@
|
||||||
namespace Tests\Browser\Schedule;
|
namespace Tests\Browser\Schedule;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\Pages\SchedulePage;
|
|
||||||
use Tests\Browser\LoginHelpers;
|
use Tests\Browser\LoginHelpers;
|
||||||
|
use Tests\Browser\Pages\SchedulePage;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class SchedulePageTest extends DuskTestCase
|
class SchedulePageTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
use LoginHelpers;
|
use LoginHelpers;
|
||||||
|
|
||||||
protected static $schedulePageTestPlanner = null;
|
protected static $schedulePageTestPlanner = null;
|
||||||
|
|
||||||
protected static $schedulePageTestEmail = null;
|
protected static $schedulePageTestEmail = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
|
|
@ -28,30 +29,30 @@ protected function tearDown(): void
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanAccessSchedulePage(): void
|
public function test_can_access_schedule_page(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToSchedule($browser);
|
$this->loginAndGoToSchedule($browser);
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
->assertSee('SCHEDULE')
|
->assertSee('SCHEDULE')
|
||||||
->assertSee('Generate Schedule');
|
->assertSee('Generate Schedule');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSchedulePageHasMonthNavigation(): void
|
public function test_schedule_page_has_month_navigation(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToSchedule($browser);
|
$this->loginAndGoToSchedule($browser);
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
->assertPresent('@previous-month')
|
->assertPresent('@previous-month')
|
||||||
->assertPresent('@next-month')
|
->assertPresent('@next-month')
|
||||||
->assertSee(now()->format('F Y'));
|
->assertSee(now()->format('F Y'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanNavigateToNextMonth(): void
|
public function test_can_navigate_to_next_month(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToSchedule($browser);
|
$this->loginAndGoToSchedule($browser);
|
||||||
|
|
@ -59,12 +60,12 @@ public function testCanNavigateToNextMonth(): void
|
||||||
$nextMonth = now()->addMonth();
|
$nextMonth = now()->addMonth();
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
->goToNextMonth()
|
->goToNextMonth()
|
||||||
->assertSee($nextMonth->format('F Y'));
|
->assertSee($nextMonth->format('F Y'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanNavigateToPreviousMonth(): void
|
public function test_can_navigate_to_previous_month(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToSchedule($browser);
|
$this->loginAndGoToSchedule($browser);
|
||||||
|
|
@ -72,36 +73,36 @@ public function testCanNavigateToPreviousMonth(): void
|
||||||
$prevMonth = now()->subMonth();
|
$prevMonth = now()->subMonth();
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
->goToPreviousMonth()
|
->goToPreviousMonth()
|
||||||
->assertSee($prevMonth->format('F Y'));
|
->assertSee($prevMonth->format('F Y'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testScheduleGeneratorShowsUserSelection(): void
|
public function test_schedule_generator_shows_user_selection(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToSchedule($browser);
|
$this->loginAndGoToSchedule($browser);
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
->assertSee('Select Users')
|
->assertSee('Select Users')
|
||||||
->assertPresent('@generate-button')
|
->assertPresent('@generate-button')
|
||||||
->assertPresent('@clear-month-button');
|
->assertPresent('@clear-month-button');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCalendarDisplaysDaysOfWeek(): void
|
public function test_calendar_displays_days_of_week(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToSchedule($browser);
|
$this->loginAndGoToSchedule($browser);
|
||||||
|
|
||||||
$browser->on(new SchedulePage)
|
$browser->on(new SchedulePage)
|
||||||
->assertSee('Mon')
|
->assertSee('Mon')
|
||||||
->assertSee('Tue')
|
->assertSee('Tue')
|
||||||
->assertSee('Wed')
|
->assertSee('Wed')
|
||||||
->assertSee('Thu')
|
->assertSee('Thu')
|
||||||
->assertSee('Fri')
|
->assertSee('Fri')
|
||||||
->assertSee('Sat')
|
->assertSee('Sat')
|
||||||
->assertSee('Sun');
|
->assertSee('Sun');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,18 @@
|
||||||
namespace Tests\Browser\Users;
|
namespace Tests\Browser\Users;
|
||||||
|
|
||||||
use Laravel\Dusk\Browser;
|
use Laravel\Dusk\Browser;
|
||||||
use Tests\DuskTestCase;
|
|
||||||
use Tests\Browser\Pages\UsersPage;
|
|
||||||
use Tests\Browser\LoginHelpers;
|
use Tests\Browser\LoginHelpers;
|
||||||
|
use Tests\Browser\Pages\UsersPage;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
class CreateUserTest extends DuskTestCase
|
class CreateUserTest extends DuskTestCase
|
||||||
{
|
{
|
||||||
use LoginHelpers;
|
use LoginHelpers;
|
||||||
|
|
||||||
protected static $createUserTestPlanner = null;
|
protected static $createUserTestPlanner = null;
|
||||||
|
|
||||||
protected static $createUserTestEmail = null;
|
protected static $createUserTestEmail = null;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
@ -21,7 +22,7 @@ protected function setUp(): void
|
||||||
self::$testPlanner = self::$createUserTestPlanner;
|
self::$testPlanner = self::$createUserTestPlanner;
|
||||||
self::$testEmail = self::$createUserTestEmail;
|
self::$testEmail = self::$createUserTestEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
// Save the planner for next test method in this class
|
// Save the planner for next test method in this class
|
||||||
|
|
@ -30,74 +31,74 @@ protected function tearDown(): void
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanAccessUsersPage(): void
|
public function test_can_access_users_page(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToUsers($browser);
|
$this->loginAndGoToUsers($browser);
|
||||||
|
|
||||||
$browser->on(new UsersPage)
|
$browser->on(new UsersPage)
|
||||||
->assertSee('MANAGE USERS')
|
->assertSee('MANAGE USERS')
|
||||||
->assertSee('Add User');
|
->assertSee('Add User');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanOpenCreateUserModal(): void
|
public function test_can_open_create_user_modal(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToUsers($browser);
|
$this->loginAndGoToUsers($browser);
|
||||||
|
|
||||||
$browser->on(new UsersPage)
|
$browser->on(new UsersPage)
|
||||||
->openCreateModal()
|
->openCreateModal()
|
||||||
->assertSee('Add New User')
|
->assertSee('Add New User')
|
||||||
->assertSee('Name')
|
->assertSee('Name')
|
||||||
->assertSee('Cancel')
|
->assertSee('Cancel')
|
||||||
->assertSee('Create User');
|
->assertSee('Create User');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateUserFormValidation(): void
|
public function test_create_user_form_validation(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToUsers($browser);
|
$this->loginAndGoToUsers($browser);
|
||||||
|
|
||||||
$browser->on(new UsersPage)
|
$browser->on(new UsersPage)
|
||||||
->openCreateModal()
|
->openCreateModal()
|
||||||
->press('Create User')
|
->press('Create User')
|
||||||
->pause(self::PAUSE_MEDIUM)
|
->pause(self::PAUSE_MEDIUM)
|
||||||
->assertSee('The name field is required');
|
->assertSee('The name field is required');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanCreateUser(): void
|
public function test_can_create_user(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$userName = 'TestCreate_' . uniqid();
|
$userName = 'TestCreate_'.uniqid();
|
||||||
|
|
||||||
$this->loginAndGoToUsers($browser);
|
$this->loginAndGoToUsers($browser);
|
||||||
|
|
||||||
$browser->on(new UsersPage)
|
$browser->on(new UsersPage)
|
||||||
->openCreateModal()
|
->openCreateModal()
|
||||||
->type('input[wire\\:model="name"]', $userName)
|
->type('input[wire\\:model="name"]', $userName)
|
||||||
->press('Create User')
|
->press('Create User')
|
||||||
->pause(self::PAUSE_MEDIUM)
|
->pause(self::PAUSE_MEDIUM)
|
||||||
->assertSee('User created successfully')
|
->assertSee('User created successfully')
|
||||||
->assertSee($userName);
|
->assertSee($userName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanCancelUserCreation(): void
|
public function test_can_cancel_user_creation(): void
|
||||||
{
|
{
|
||||||
$this->browse(function (Browser $browser) {
|
$this->browse(function (Browser $browser) {
|
||||||
$this->loginAndGoToUsers($browser);
|
$this->loginAndGoToUsers($browser);
|
||||||
|
|
||||||
$browser->on(new UsersPage)
|
$browser->on(new UsersPage)
|
||||||
->openCreateModal()
|
->openCreateModal()
|
||||||
->type('input[wire\\:model="name"]', 'Test Cancel User')
|
->type('input[wire\\:model="name"]', 'Test Cancel User')
|
||||||
->press('Cancel')
|
->press('Cancel')
|
||||||
->pause(self::PAUSE_SHORT)
|
->pause(self::PAUSE_SHORT)
|
||||||
// Modal should be closed, we should be back on users page
|
// Modal should be closed, we should be back on users page
|
||||||
->assertSee('MANAGE USERS')
|
->assertSee('MANAGE USERS')
|
||||||
->assertDontSee('Add New User');
|
->assertDontSee('Add New User');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
use Facebook\WebDriver\Chrome\ChromeOptions;
|
use Facebook\WebDriver\Chrome\ChromeOptions;
|
||||||
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Laravel\Dusk\TestCase as BaseTestCase;
|
use Laravel\Dusk\TestCase as BaseTestCase;
|
||||||
use PHPUnit\Framework\Attributes\BeforeClass;
|
use PHPUnit\Framework\Attributes\BeforeClass;
|
||||||
|
|
||||||
|
|
@ -13,8 +12,11 @@ abstract class DuskTestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
// Timeout constants for consistent timing across all Dusk tests
|
// Timeout constants for consistent timing across all Dusk tests
|
||||||
public const TIMEOUT_SHORT = 2; // 2 seconds max for most operations
|
public const TIMEOUT_SHORT = 2; // 2 seconds max for most operations
|
||||||
|
|
||||||
public const TIMEOUT_MEDIUM = 3; // 3 seconds for slower operations
|
public const TIMEOUT_MEDIUM = 3; // 3 seconds for slower operations
|
||||||
|
|
||||||
public const PAUSE_SHORT = 500; // 0.5 seconds for quick pauses
|
public const PAUSE_SHORT = 500; // 0.5 seconds for quick pauses
|
||||||
|
|
||||||
public const PAUSE_MEDIUM = 1000; // 1 second for medium pauses
|
public const PAUSE_MEDIUM = 1000; // 1 second for medium pauses
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
|
||||||
use Tests\TestCase;
|
|
||||||
use App\Models\Planner;
|
use App\Models\Planner;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
class AuthenticationTest extends TestCase
|
class AuthenticationTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
@ -61,10 +61,6 @@ public function test_session_is_created_on_login_page(): void
|
||||||
// Check if CSRF token is generated
|
// Check if CSRF token is generated
|
||||||
$this->assertNotNull(csrf_token());
|
$this->assertNotNull(csrf_token());
|
||||||
|
|
||||||
// Check session driver
|
|
||||||
$sessionDriver = config('session.driver');
|
|
||||||
$this->assertNotEquals('array', $sessionDriver, 'Session driver should not be array for authentication');
|
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
$response->assertSessionHasNoErrors();
|
$response->assertSessionHasNoErrors();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
namespace Tests\Feature\Dish;
|
namespace Tests\Feature\Dish;
|
||||||
|
|
||||||
use App\Models\Dish;
|
use App\Models\Dish;
|
||||||
use App\Models\Planner;
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Testing\Fluent\AssertableJson;
|
use Illuminate\Testing\Fluent\AssertableJson;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,6 @@ public function test_it_deletes_user_dishes_when_deleting_a_dish(): void
|
||||||
$this->assertDatabaseEmpty(UserDish::class);
|
$this->assertDatabaseEmpty(UserDish::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function test_planner_cannot_delete_dish_from_other_planner(): void
|
public function test_planner_cannot_delete_dish_from_other_planner(): void
|
||||||
{
|
{
|
||||||
$planner = $this->planner;
|
$planner = $this->planner;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use Tests\TestCase;
|
|
||||||
use App\Models\Planner;
|
use App\Models\Planner;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
class RegistrationTest extends TestCase
|
class RegistrationTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
@ -13,7 +13,7 @@ class RegistrationTest extends TestCase
|
||||||
public function test_registration_screen_can_be_rendered()
|
public function test_registration_screen_can_be_rendered()
|
||||||
{
|
{
|
||||||
$response = $this->get('/register');
|
$response = $this->get('/register');
|
||||||
|
|
||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
$response->assertViewIs('auth.register');
|
$response->assertViewIs('auth.register');
|
||||||
$response->assertSee('Register');
|
$response->assertSee('Register');
|
||||||
|
|
@ -30,7 +30,7 @@ public function test_new_users_can_register()
|
||||||
|
|
||||||
$this->assertAuthenticated();
|
$this->assertAuthenticated();
|
||||||
$response->assertRedirect('/dashboard');
|
$response->assertRedirect('/dashboard');
|
||||||
|
|
||||||
// Check user was created
|
// Check user was created
|
||||||
$this->assertDatabaseHas('planners', [
|
$this->assertDatabaseHas('planners', [
|
||||||
'email' => 'test@example.com',
|
'email' => 'test@example.com',
|
||||||
|
|
@ -68,10 +68,10 @@ public function test_registration_fails_with_password_mismatch()
|
||||||
$response->assertRedirect();
|
$response->assertRedirect();
|
||||||
$response->assertSessionHasErrors('password');
|
$response->assertSessionHasErrors('password');
|
||||||
$this->assertGuest();
|
$this->assertGuest();
|
||||||
|
|
||||||
// Check user was not created
|
// Check user was not created
|
||||||
$this->assertDatabaseMissing('planners', [
|
$this->assertDatabaseMissing('planners', [
|
||||||
'email' => 'test@example.com',
|
'email' => 'test@example.com',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,11 @@
|
||||||
namespace Tests\Feature\Schedule;
|
namespace Tests\Feature\Schedule;
|
||||||
|
|
||||||
use App\Models\Dish;
|
use App\Models\Dish;
|
||||||
use App\Models\Planner;
|
|
||||||
use App\Models\UserDish;
|
|
||||||
use App\Models\UserDishRecurrence;
|
|
||||||
use App\Models\Schedule;
|
use App\Models\Schedule;
|
||||||
use App\Models\ScheduledUserDish;
|
use App\Models\ScheduledUserDish;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\UserDish;
|
||||||
|
use App\Models\UserDishRecurrence;
|
||||||
use App\Models\WeeklyRecurrence;
|
use App\Models\WeeklyRecurrence;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
|
@ -115,6 +114,7 @@ public function test_fresh_schedule_adheres_to_fixed_recurrences(): void
|
||||||
|
|
||||||
$this->assertContains($targetUserDish->id, $targetScheduledUserDishes);
|
$this->assertContains($targetUserDish->id, $targetScheduledUserDishes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_schedule_can_be_overwritten(): void
|
public function test_schedule_can_be_overwritten(): void
|
||||||
{
|
{
|
||||||
$planner = $this->planner;
|
$planner = $this->planner;
|
||||||
|
|
@ -132,8 +132,7 @@ public function test_schedule_can_be_overwritten(): void
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert that every user has `UserDish` records
|
// Assert that every user has `UserDish` records
|
||||||
$users->each(fn (User $user) =>
|
$users->each(fn (User $user) => $this->assertNotEmpty($user->refresh()->userDishes)
|
||||||
$this->assertNotEmpty($user->refresh()->userDishes)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$scheduleDay = Schedule::factory()
|
$scheduleDay = Schedule::factory()
|
||||||
|
|
@ -172,7 +171,7 @@ public function test_schedule_can_be_overwritten(): void
|
||||||
|
|
||||||
$this->assertDatabaseCount(Schedule::class, 14);
|
$this->assertDatabaseCount(Schedule::class, 14);
|
||||||
|
|
||||||
$freshScheduleDay = Schedule::query()->where('date', $scheduleDay->date)->first();
|
$freshScheduleDay = Schedule::query()->whereDate('date', $scheduleDay->date)->first();
|
||||||
$this->assertNotEquals(
|
$this->assertNotEquals(
|
||||||
$originalUserDishes,
|
$originalUserDishes,
|
||||||
$freshScheduleDay->scheduledUserDishes->map(fn (ScheduledUserDish $scheduledUserDish) => [
|
$freshScheduleDay->scheduledUserDishes->map(fn (ScheduledUserDish $scheduledUserDish) => [
|
||||||
|
|
@ -218,11 +217,11 @@ public function test_fixed_recurrence_takes_precedence_during_overwrite(): void
|
||||||
|
|
||||||
$users
|
$users
|
||||||
->map(fn (User $user) => ScheduledUserDish::factory()
|
->map(fn (User $user) => ScheduledUserDish::factory()
|
||||||
->create([
|
->create([
|
||||||
'schedule_id' => $scheduleDay->id,
|
'schedule_id' => $scheduleDay->id,
|
||||||
'user_dish_id' => $user->userDishes->random()->id,
|
'user_dish_id' => $user->userDishes->random()->id,
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
ScheduledUserDish::factory()
|
ScheduledUserDish::factory()
|
||||||
->schedule($scheduleDay)
|
->schedule($scheduleDay)
|
||||||
|
|
@ -235,7 +234,6 @@ public function test_fixed_recurrence_takes_precedence_during_overwrite(): void
|
||||||
$this->assertDatabaseCount(Schedule::class, 1);
|
$this->assertDatabaseCount(Schedule::class, 1);
|
||||||
$this->assertDatabaseCount(ScheduledUserDish::class, 2);
|
$this->assertDatabaseCount(ScheduledUserDish::class, 2);
|
||||||
|
|
||||||
|
|
||||||
$this
|
$this
|
||||||
->actingAs($planner)
|
->actingAs($planner)
|
||||||
->post(route('api.schedule.generate'), [
|
->post(route('api.schedule.generate'), [
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue