mirror of https://github.com/kcal-app/kcal.git
Add support for meals customization (#15)
* Add meals as JSON field on User * Change journal entry meal field to integer * Make Quill and Draggable more modular * Add meals routes and controller (WIP) * Set default meals during migration * Handle meals form * No-op meal field migrations This allows tests to pass easier and the migrations are not entirely necessary anyway. * Update factories and tests for new meals format * Add basic meals edit test * Update journal entry processing for new meal values * Remove temporary migrations * Only use default enabled meals in tests * Add User `meals_enabled` attribute * Ensure NULL values are removed from attributes * Add meals data to use API response * Set default meals on user create
This commit is contained in:
parent
c15f81ee6b
commit
1fbb9a7dae
|
@ -122,7 +122,6 @@ class JournalEntryController extends Controller
|
|||
|
||||
return view('journal-entries.create')
|
||||
->with('ingredients', $ingredients)
|
||||
->with('meals', JournalEntry::meals()->toArray())
|
||||
->with('units', Nutrients::units()->toArray())
|
||||
->with('default_date', Carbon::createFromFormat('Y-m-d', $date));
|
||||
}
|
||||
|
@ -134,7 +133,6 @@ class JournalEntryController extends Controller
|
|||
{
|
||||
$date = $request->date ?? Carbon::now()->toDateString();
|
||||
return view('journal-entries.create-from-nutrients')
|
||||
->with('meals', JournalEntry::meals()->toArray())
|
||||
->with('units', Nutrients::units()->toArray())
|
||||
->with('default_date', Carbon::createFromFormat('Y-m-d', $date));
|
||||
}
|
||||
|
@ -279,8 +277,9 @@ class JournalEntryController extends Controller
|
|||
*/
|
||||
public function storeFromNutrients(StoreFromNutrientsJournalEntryRequest $request): RedirectResponse {
|
||||
$attributes = $request->validated();
|
||||
$entry = JournalEntry::make(array_filter($attributes))
|
||||
->user()->associate(Auth::user());
|
||||
$entry = JournalEntry::make(array_filter($attributes, function ($value) {
|
||||
return !is_null($value);
|
||||
}))->user()->associate(Auth::user());
|
||||
$entry->save();
|
||||
session()->flash('message', "Journal entry added!");
|
||||
return redirect()->route(
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Rules\ArrayNotEmpty;
|
||||
use App\Support\ArrayFormat;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class MealsController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Show the form for editing a user's meals data.
|
||||
*/
|
||||
public function edit(): View
|
||||
{
|
||||
return view('meals.edit')->with('meals', Auth::user()->meals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user profile data.
|
||||
*/
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$attributes = $request->validate([
|
||||
'meal' => ['required', new ArrayNotEmpty],
|
||||
'meal.value.*' => ['required', 'numeric'],
|
||||
'meal.weight.*' => ['required', 'numeric'],
|
||||
'meal.label.*' => ['nullable', 'string'],
|
||||
'meal.enabled.*' => ['required', 'boolean'],
|
||||
]);
|
||||
|
||||
$user = Auth::user();
|
||||
$user->meals = ArrayFormat::flipTwoDimensionalKeys($attributes['meal']);
|
||||
$user->save();
|
||||
session()->flash('message', "Meals customizations updated!");
|
||||
return redirect()->route('meals.edit');
|
||||
}
|
||||
|
||||
}
|
|
@ -2,12 +2,9 @@
|
|||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\JournalEntry;
|
||||
use App\Rules\ArrayNotEmpty;
|
||||
use App\Rules\InArray;
|
||||
use App\Rules\StringIsPositiveDecimalOrFraction;
|
||||
use App\Rules\UsesIngredientTrait;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class StoreFromNutrientsJournalEntryRequest extends FormRequest
|
||||
{
|
||||
|
@ -21,8 +18,7 @@ class StoreFromNutrientsJournalEntryRequest extends FormRequest
|
|||
'date' => ['required', 'date'],
|
||||
'meal' => [
|
||||
'required',
|
||||
'string',
|
||||
new InArray(JournalEntry::meals()->pluck('value')->toArray())
|
||||
new InArray(Auth::user()->meals_enabled->pluck('value')->toArray())
|
||||
],
|
||||
'summary' => ['required', 'string'],
|
||||
'calories' => ['nullable', 'numeric', 'min:0', 'required_without_all:fat,cholesterol,sodium,carbohydrates,protein'],
|
||||
|
|
|
@ -8,6 +8,7 @@ use App\Rules\InArray;
|
|||
use App\Rules\StringIsPositiveDecimalOrFraction;
|
||||
use App\Rules\UsesIngredientTrait;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class StoreJournalEntryRequest extends FormRequest
|
||||
{
|
||||
|
@ -22,10 +23,9 @@ class StoreJournalEntryRequest extends FormRequest
|
|||
'ingredients.date.*' => ['nullable', 'date', 'required_with:ingredients.id.*'],
|
||||
'ingredients.meal' => ['required', 'array', new ArrayNotEmpty],
|
||||
'ingredients.meal.*' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'required',
|
||||
'required_with:ingredients.id.*',
|
||||
new InArray(JournalEntry::meals()->pluck('value')->toArray())
|
||||
new InArray(Auth::user()->meals_enabled->pluck('value')->toArray())
|
||||
],
|
||||
'ingredients.amount' => ['required', 'array', new ArrayNotEmpty],
|
||||
'ingredients.amount.*' => ['required_with:ingredients.id.*', 'nullable', new StringIsPositiveDecimalOrFraction],
|
||||
|
|
|
@ -29,6 +29,7 @@ class UserSchema extends SchemaProvider
|
|||
return [
|
||||
'username' => $resource->username,
|
||||
'name' => $resource->name,
|
||||
'meals' => $resource->meals,
|
||||
'createdAt' => $resource->created_at,
|
||||
'updatedAt' => $resource->updated_at,
|
||||
];
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Sluggable;
|
||||
use Illuminate\Database\Eloquent\Casts\AsCollection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
|
@ -51,6 +53,9 @@ use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
|||
* @mixin \Eloquent
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\JournalDate[] $journalDates
|
||||
* @property-read int|null $journal_dates_count
|
||||
* @property \Illuminate\Support\Collection|null $meals
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereMeals($value)
|
||||
* @property-read Collection $meals_enabled
|
||||
*/
|
||||
final class User extends Authenticatable implements HasMedia
|
||||
{
|
||||
|
@ -59,6 +64,16 @@ final class User extends Authenticatable implements HasMedia
|
|||
use Notifiable;
|
||||
use Sluggable;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected static function booted(): void {
|
||||
static::creating(function (User $user) {
|
||||
// Set default meals configuration.
|
||||
$user->meals = User::getDefaultMeals();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -66,6 +81,7 @@ final class User extends Authenticatable implements HasMedia
|
|||
'username',
|
||||
'password',
|
||||
'name',
|
||||
'meals',
|
||||
'admin',
|
||||
];
|
||||
|
||||
|
@ -82,8 +98,25 @@ final class User extends Authenticatable implements HasMedia
|
|||
*/
|
||||
protected $casts = [
|
||||
'admin' => 'bool',
|
||||
'meals' => AsCollection::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the default meals structure.
|
||||
*/
|
||||
public static function getDefaultMeals(): Collection {
|
||||
$meals = new Collection();
|
||||
for ($i = 0; $i <= 7; $i++) {
|
||||
$meals->add([
|
||||
'value' => $i,
|
||||
'label' => 'Meal ' . ($i + 1),
|
||||
'weight' => $i,
|
||||
'enabled' => $i < 3,
|
||||
]);
|
||||
}
|
||||
return $meals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -113,6 +146,13 @@ final class User extends Authenticatable implements HasMedia
|
|||
return $this->hasMany(JournalEntry::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User's enabled meals, sorted by weight.
|
||||
*/
|
||||
public function getMealsEnabledAttribute(): Collection {
|
||||
return $this->meals->where('enabled', true)->sortBy('weight');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's goal (if one exists) for a specific date.
|
||||
*
|
||||
|
|
|
@ -11,7 +11,10 @@ class ArrayNotEmpty implements Rule
|
|||
*/
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
return !empty(array_filter($value));
|
||||
return !empty(array_filter($value, function ($value) {
|
||||
// Allow other "empty-y" values like false and 0.
|
||||
return $value !== null;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,7 @@ class JournalEntryFactory extends Factory
|
|||
'sodium' => $this->faker->randomFloat(1, 0, 500),
|
||||
'carbohydrates' => $this->faker->randomFloat(1, 0, 40),
|
||||
'protein' => $this->faker->randomFloat(1, 0, 20),
|
||||
'meal' => $this->faker->randomElement(['breakfast', 'lunch', 'dinner', 'snacks']),
|
||||
'meal' => User::getDefaultMeals()->where('enabled', true)->pluck('value')->random(),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ class CreateUsersTable extends Migration
|
|||
$table->string('username')->unique();
|
||||
$table->string('password');
|
||||
$table->string('name');
|
||||
$table->json('meals')->nullable();
|
||||
$table->boolean('admin')->default(false);
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
|
|
|
@ -26,7 +26,7 @@ class CreateJournalEntriesTable extends Migration
|
|||
$table->unsignedFloat('sodium')->default(0);
|
||||
$table->unsignedFloat('carbohydrates')->default(0);
|
||||
$table->unsignedFloat('protein')->default(0);
|
||||
$table->enum('meal', JournalEntry::meals()->pluck('value')->toArray());
|
||||
$table->unsignedInteger('meal');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,15 @@
|
|||
/*!
|
||||
* Quill Editor v1.3.7
|
||||
* https://quilljs.com/
|
||||
* Copyright (c) 2014, Jason Chen
|
||||
* Copyright (c) 2013, salesforce.com
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <http://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,8 @@
|
|||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <http://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"/js/app.js": "/js/app.js",
|
||||
"/js/recipes/edit.js": "/js/recipes/edit.js",
|
||||
"/js/draggable.js": "/js/draggable.js",
|
||||
"/js/quill.js": "/js/quill.js",
|
||||
"/css/recipes/edit.css": "/css/recipes/edit.css",
|
||||
"/css/app.css": "/css/app.css"
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
window.Draggable = require('@shopify/draggable');
|
|
@ -1,11 +1,2 @@
|
|||
import Quill from 'quill/core';
|
||||
|
||||
import Toolbar from 'quill/modules/toolbar';
|
||||
import Snow from 'quill/themes/snow';
|
||||
|
||||
Quill.register({
|
||||
'modules/toolbar': Toolbar,
|
||||
'themes/snow': Snow,
|
||||
});
|
||||
|
||||
export default Quill;
|
||||
require('./quill.module');
|
||||
window.Quill = require('quill');
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import Quill from 'quill/core';
|
||||
|
||||
import Toolbar from 'quill/modules/toolbar';
|
||||
import Snow from 'quill/themes/snow';
|
||||
|
||||
Quill.register({
|
||||
'modules/toolbar': Toolbar,
|
||||
'themes/snow': Snow,
|
||||
});
|
||||
|
||||
export default Quill;
|
|
@ -1,4 +0,0 @@
|
|||
require('../quill');
|
||||
|
||||
window.Draggable = require('@shopify/draggable');
|
||||
window.Quill = require('quill');
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<x-inputs.select name="meal"
|
||||
class="block w-full"
|
||||
:options="$meals"
|
||||
:options="Auth::user()->meals_enabled->toArray()"
|
||||
:selectedValue="old('meal')"
|
||||
:hasError="$errors->has('meal')"
|
||||
required>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<span class="font-bold">Meal:</span>
|
||||
<span>{{ $journal_entry->meal }}</span>
|
||||
<span>{{ \Illuminate\Support\Facades\Auth::user()->meals->firstWhere('value', $journal_entry->meal)['label'] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<x-inputs.button class="bg-red-800 hover:bg-red-700">
|
||||
|
|
|
@ -135,21 +135,21 @@
|
|||
</section>
|
||||
</div>
|
||||
<div class="w-full sm:w-3/5 md:w-2/3 lg:w-3/4 flex flex-col space-y-4">
|
||||
@foreach(['breakfast', 'lunch', 'dinner', 'snacks'] as $meal)
|
||||
@foreach(\Illuminate\Support\Facades\Auth::user()->meals_enabled as $meal)
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg text-gray-800">
|
||||
<div class="flex items-center">
|
||||
<div>{{ Str::ucfirst($meal) }}</div>
|
||||
<div class="ml-2 w-full"><hr/></div>
|
||||
<div>{{ $meal['label'] }}</div>
|
||||
<div class="ml-2 flex-grow"><hr/></div>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">
|
||||
@foreach(\App\Support\Nutrients::all()->sortBy('weight') as $nutrient)
|
||||
{{ \App\Support\Nutrients::round($entries->where('meal', $meal)->sum($nutrient['value']), $nutrient['value']) }}{{ $nutrient['unit'] }}
|
||||
{{ \App\Support\Nutrients::round($entries->where('meal', $meal['value'])->sum($nutrient['value']), $nutrient['value']) }}{{ $nutrient['unit'] }}
|
||||
{{ $nutrient['value'] }}@if(!$loop->last), @endif
|
||||
@endforeach
|
||||
</span>
|
||||
</h3>
|
||||
@forelse($entries->where('meal', $meal) as $entry)
|
||||
@forelse($entries->where('meal', $meal['value']) as $entry)
|
||||
<details>
|
||||
<summary>{{ $entry->summary }}</summary>
|
||||
<div class="border-blue-100 border-2 p-2 ml-4">
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<x-inputs.label for="ingredients[meal][]" value="Meal" class="md:hidden"/>
|
||||
<x-inputs.select name="ingredients[meal][]"
|
||||
class="block w-full"
|
||||
:options="$meals"
|
||||
:options="Auth::user()->meals_enabled->toArray()"
|
||||
:selectedValue="$meal ?? null"
|
||||
:hasError="$errors->has('ingredients.meal.' . $key)"
|
||||
required>
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<div class="space-y-2">
|
||||
<x-dropdown-link :href="route('profiles.show', Auth::user())">My Profile</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('goals.index')">My Goals</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('meals.edit')">My Meals</x-dropdown-link>
|
||||
@can('administer', \App\Models\User::class)
|
||||
<hr />
|
||||
<x-dropdown-link :href="route('users.index')">Manage Users</x-dropdown-link>
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="title">My Meals</x-slot>
|
||||
<x-slot name="header">
|
||||
<h1 class="font-semibold text-xl text-gray-800 leading-tight">My Meals</h1>
|
||||
</x-slot>
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ route('meals.update') }}">
|
||||
@method('put')
|
||||
@csrf
|
||||
<div class="flex flex-col space-y-4">
|
||||
<div class="flex flex-row space-x-4 w-full items-center bg-gray-200 text-gray-600 uppercase text-sm leading-normal font-bold">
|
||||
<div class="w-1/6 sm:w-1/12 p-2"> </div>
|
||||
<div class="w-4/6 sm:w-5/6 p-2">Meal name</div>
|
||||
<div class="w-1/6 sm:w-1/12 p-2 text-center">Active</div>
|
||||
</div>
|
||||
<div x-data class="meals space-y-4">
|
||||
@foreach($meals as $key => $meal)
|
||||
<div class="meal draggable w-full">
|
||||
<x-inputs.input type="hidden" name="meal[value][]" :value="$meal['value']" />
|
||||
<x-inputs.input type="hidden" name="meal[weight][]" :value="$meal['weight'] ?? null" />
|
||||
<div class="flex flex-row space-x-4 w-full items-center">
|
||||
<div class="w-1/6 sm:w-1/12">
|
||||
<div class="draggable-handle self-center text-gray-500 bg-gray-100 p-2 cursor-move">
|
||||
<svg class="h-6 w-6 mx-auto" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<x-inputs.input name="meal[label][]"
|
||||
type="text"
|
||||
size="5"
|
||||
placeholder="Breakfast, lunch, dinner, etc."
|
||||
class="block w-4/6 sm:w-5/6"
|
||||
:value="$meal['label'] ?? null" />
|
||||
<div class="w-1/6 sm:w-1/12 text-center">
|
||||
<x-inputs.input name="meal[enabled][]"
|
||||
type="checkbox"
|
||||
value="1"
|
||||
:checked="$meal['enabled'] ?? null" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-inputs.button>Save</x-inputs.button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@once
|
||||
@push('scripts')
|
||||
<script src="{{ asset('js/draggable.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
// Activate meals sortable.
|
||||
const mealsSortable = new Draggable.Sortable(document.querySelector('.meals'), {
|
||||
draggable: '.draggable',
|
||||
handle: '.draggable-handle',
|
||||
mirror: {
|
||||
appendTo: '.meals',
|
||||
constrainDimensions: true,
|
||||
},
|
||||
})
|
||||
|
||||
// Recalculate weight (order) of all ingredients.
|
||||
mealsSortable.on('drag:stopped', (e) => {
|
||||
Array.from(e.sourceContainer.children)
|
||||
.filter(el => el.classList.contains('draggable'))
|
||||
.forEach((el, index) => {
|
||||
el.querySelector('input[name$="[weight][]"]').value = index;
|
||||
});
|
||||
})
|
||||
</script>
|
||||
@endpush
|
||||
@endonce
|
||||
</x-app-layout>
|
|
@ -182,7 +182,8 @@
|
|||
|
||||
@once
|
||||
@push('scripts')
|
||||
<script src="{{ asset('js/recipes/edit.js') }}"></script>
|
||||
<script src="{{ asset('js/draggable.js') }}"></script>
|
||||
<script src="{{ asset('js/quill.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// Enforce inline (style-base) alignment.
|
||||
|
|
|
@ -5,6 +5,7 @@ use App\Http\Controllers\GoalController;
|
|||
use App\Http\Controllers\IngredientPickerController;
|
||||
use App\Http\Controllers\JournalDateController;
|
||||
use App\Http\Controllers\JournalEntryController;
|
||||
use App\Http\Controllers\MealsController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\RecipeController;
|
||||
use App\Http\Controllers\Auth\AuthenticatedSessionController;
|
||||
|
@ -40,6 +41,10 @@ Route::middleware(['auth'])->group(function () {
|
|||
Route::resource('journal-entries', JournalEntryController::class);
|
||||
Route::get('/journal-entries/{journal_entry}/delete', [JournalEntryController::class, 'delete'])->name('journal-entries.delete');
|
||||
|
||||
// Meals.
|
||||
Route::get('/meals', [MealsController::class, 'edit'])->name('meals.edit');
|
||||
Route::put('/meals', [MealsController::class, 'update'])->name('meals.update');
|
||||
|
||||
// Recipes.
|
||||
Route::resource('recipes', RecipeController::class);
|
||||
Route::get('/recipes/{recipe}/delete', [RecipeController::class, 'delete'])->name('recipes.delete');
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Tests\Feature\Http\Controllers;
|
|||
use App\Http\Controllers\JournalEntryController;
|
||||
use App\Models\IngredientAmount;
|
||||
use App\Models\JournalEntry;
|
||||
use App\Models\User;
|
||||
use Database\Factories\JournalEntryFactory;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
|
||||
|
@ -130,7 +131,7 @@ class JournalEntryControllerTest extends HttpControllerTestCase
|
|||
/** @var \App\Models\IngredientAmount $ingredient_amount */
|
||||
foreach ($ingredient_amounts as $ingredient_amount) {
|
||||
$ingredients['date'][] = $this->faker->dateTimeThisMonth->format('Y-m-d');
|
||||
$ingredients['meal'][] = $this->faker->randomElement(JournalEntry::meals()->pluck('value')->toArray());
|
||||
$ingredients['meal'][] = $this->user->meals_enabled->pluck('value')->random();
|
||||
$ingredients['name'][] = $ingredient_amount->ingredient->name;
|
||||
$ingredients['amount'][] = $ingredient_amount->amount;
|
||||
$ingredients['unit'][] = $ingredient_amount->unit;
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\MealsController;
|
||||
use Tests\LoggedInTestCase;
|
||||
|
||||
class MealsControllerTest extends LoggedInTestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* Test editing meals.
|
||||
*/
|
||||
public function testCanEditMeals(): void
|
||||
{
|
||||
$edit_url = action([MealsController::class, 'edit']);
|
||||
$response = $this->get($edit_url);
|
||||
$response->assertOk();
|
||||
|
||||
$meal_data = [];
|
||||
$this->user->meals->each(function (array $meal) use (&$meal_data) {
|
||||
$meal_data['value'][] = $meal['value'];
|
||||
$meal_data['weight'][] = $meal['weight'];
|
||||
$meal_data['label'][] = "Updated {$meal['label']}";
|
||||
$meal_data['enabled'][] = $meal['enabled'] ?? false;
|
||||
});
|
||||
$put_url = action([MealsController::class, 'update']);
|
||||
$response = $this->put($put_url, ['meal' => $meal_data]);
|
||||
$response->assertSessionHasNoErrors();
|
||||
|
||||
$this->user->refresh();
|
||||
$this->user->meals->each(function (array $meal) {
|
||||
$this->assertStringStartsWith('Updated', $meal['label']);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -13,7 +13,8 @@ const mix = require('laravel-mix');
|
|||
|
||||
mix
|
||||
.js('resources/js/app.js', 'public/js')
|
||||
.js('resources/js/recipes/edit.js', 'public/js/recipes')
|
||||
.js('resources/js/draggable.js', 'public/js')
|
||||
.js('resources/js/quill.js', 'public/js')
|
||||
.postCss('resources/css/app.css', 'public/css', [
|
||||
require('postcss-import'),
|
||||
require('tailwindcss'),
|
||||
|
|
Loading…
Reference in New Issue