diff --git a/app/Http/Controllers/EntryController.php b/app/Http/Controllers/EntryController.php index 6c16d8f..9396f2e 100644 --- a/app/Http/Controllers/EntryController.php +++ b/app/Http/Controllers/EntryController.php @@ -56,7 +56,7 @@ class EntryController extends Controller } $entry->delete(); - return redirect('/entries')->with('success', 'The '.$entry->audition->name.'entry for '.$entry->student->full_name().'has been deleted.'); + return redirect()->route('entries.index')->with('success', 'The '.$entry->audition->name.'entry for '.$entry->student->full_name().'has been deleted.'); } } diff --git a/app/Observers/EntryObserver.php b/app/Observers/EntryObserver.php index 67a51cf..c18ec21 100644 --- a/app/Observers/EntryObserver.php +++ b/app/Observers/EntryObserver.php @@ -13,8 +13,7 @@ class EntryObserver */ public function created(Entry $entry): void { - AuditionChange::dispatch(); - EntryChange::dispatch($entry->audition_id); + } /** @@ -22,8 +21,7 @@ class EntryObserver */ public function updated(Entry $entry): void { - AuditionChange::dispatch(); - EntryChange::dispatch($entry->audition_id); + } /** @@ -31,8 +29,7 @@ class EntryObserver */ public function deleted(Entry $entry): void { - AuditionChange::dispatch(); - EntryChange::dispatch($entry->audition_id); + } /** @@ -40,8 +37,7 @@ class EntryObserver */ public function restored(Entry $entry): void { - AuditionChange::dispatch(); - EntryChange::dispatch($entry->audition_id); + } /** @@ -49,6 +45,6 @@ class EntryObserver */ public function forceDeleted(Entry $entry): void { - EntryChange::dispatch($entry->audition_id); + } } diff --git a/app/Policies/EntryPolicy.php b/app/Policies/EntryPolicy.php index e080f66..051f529 100644 --- a/app/Policies/EntryPolicy.php +++ b/app/Policies/EntryPolicy.php @@ -4,7 +4,7 @@ namespace App\Policies; use App\Models\Entry; use App\Models\User; -use Illuminate\Auth\Access\Response; + use function is_null; class EntryPolicy @@ -14,7 +14,7 @@ class EntryPolicy */ public function viewAny(User $user): bool { - // + return true; } /** @@ -22,7 +22,10 @@ class EntryPolicy */ public function view(User $user, Entry $entry): bool { - if($user->is_admin) return true; + if ($user->is_admin) { + return true; + } + return $user->school_id == $entry->student()->school_id; } @@ -31,7 +34,10 @@ class EntryPolicy */ public function create(User $user): bool { - if($user->is_admin) return true; + if ($user->is_admin) { + return true; + } + return ! is_null($user->school_id); } @@ -40,7 +46,10 @@ class EntryPolicy */ public function update(User $user, Entry $entry): bool { - if($user->is_admin) return true; + if ($user->is_admin) { + return true; + } + return $user->school_id == $entry->student()->school_id; } @@ -49,13 +58,15 @@ class EntryPolicy */ public function delete(User $user, Entry $entry): bool { - if($user->is_admin) return true; + if ($user->is_admin) { + return true; + } // Return false if $entry->audition->entry_deadline is in the past, continue if not if ($entry->audition->entry_deadline < now()) { return false; } - return $user->school_id == $entry->student()->school_id; + return $user->school_id == $entry->student->school_id; } /** @@ -63,7 +74,7 @@ class EntryPolicy */ public function restore(User $user, Entry $entry): bool { - // + return true; } /** @@ -71,6 +82,6 @@ class EntryPolicy */ public function forceDelete(User $user, Entry $entry): bool { - // + return true; } } diff --git a/database/factories/AuditionFactory.php b/database/factories/AuditionFactory.php index 9084a08..a5b83ed 100644 --- a/database/factories/AuditionFactory.php +++ b/database/factories/AuditionFactory.php @@ -45,8 +45,8 @@ class AuditionFactory extends Factory 'score_order' => $this->faker->numberBetween(2, 50), 'entry_deadline' => Carbon::tomorrow(), 'entry_fee' => 1000, - 'minimum_grade' => 7, - 'maximum_grade' => 12, + 'minimum_grade' => $this->faker->numberBetween(7, 9), + 'maximum_grade' => $this->faker->numberBetween(8, 12), 'for_seating' => 1, 'for_advancement' => 1, ]; diff --git a/resources/views/components/icons/checkmark.blade.php b/resources/views/components/icons/checkmark.blade.php index 3231384..e781c65 100644 --- a/resources/views/components/icons/checkmark.blade.php +++ b/resources/views/components/icons/checkmark.blade.php @@ -1,4 +1,5 @@ -@props(['color' => 'currentColor']) +@props(['color' => 'currentColor', 'title'=>false]) diff --git a/resources/views/components/icons/circle-slash-no.blade.php b/resources/views/components/icons/circle-slash-no.blade.php index 1bedb5e..187593a 100644 --- a/resources/views/components/icons/circle-slash-no.blade.php +++ b/resources/views/components/icons/circle-slash-no.blade.php @@ -1,4 +1,5 @@ -@props(['color' => 'currentColor']) +@props(['color' => 'currentColor', 'title' => false]) diff --git a/resources/views/components/icons/hamburger-menu.blade.php b/resources/views/components/icons/hamburger-menu.blade.php index 3097cd3..eccb66c 100644 --- a/resources/views/components/icons/hamburger-menu.blade.php +++ b/resources/views/components/icons/hamburger-menu.blade.php @@ -1,3 +1,5 @@ +@props(['title' => false]) diff --git a/resources/views/components/icons/thumbs-down.blade.php b/resources/views/components/icons/thumbs-down.blade.php index 1990c19..7427f65 100644 --- a/resources/views/components/icons/thumbs-down.blade.php +++ b/resources/views/components/icons/thumbs-down.blade.php @@ -1,4 +1,5 @@ -@props(['color' => 'currentColor']) +@props(['color' => 'currentColor','title'=>false]) diff --git a/resources/views/components/icons/thumbs-up.blade.php b/resources/views/components/icons/thumbs-up.blade.php index 1f5c405..6a97b0a 100644 --- a/resources/views/components/icons/thumbs-up.blade.php +++ b/resources/views/components/icons/thumbs-up.blade.php @@ -1,4 +1,5 @@ -@props(['color' => 'currentColor']) +@props(['color' => 'currentColor','title'=>false]) diff --git a/resources/views/entries/index.blade.php b/resources/views/entries/index.blade.php index 1838d73..80e9d3a 100644 --- a/resources/views/entries/index.blade.php +++ b/resources/views/entries/index.blade.php @@ -9,7 +9,7 @@ Add Entry - + @@ -81,23 +81,33 @@ @if(auditionSetting('advanceTo')) @if($entry->for_seating) - +
+ +
@else - +
+ +
@endif
@if($entry->for_advancement) - +
+ + +
@else - +
+ + +
@endif
@endif @if( $entry->audition->entry_deadline >= now()) -
+ @csrf @method('DELETE') school = School::factory()->create(); + $this->user = User::factory()->create(['school_id' => $this->school->id]); +}); + +it('works for a user with a school', function () { + actingAs($this->user) + ->get(route('entries.index')) + ->assertOk(); +}); + +it('denies a user without a school', function () { + get(route('entries.index')) + ->assertRedirect(route('home')); + + actingAs(User::factory()->create()) + ->get(route('entries.index')) + ->assertForbidden(); + +}); + +it('has appropriate students in JS array for select', function () { + // Arrange + $student = Student::factory()->create(['school_id' => $this->school->id]); + // Act & Assert + actingAs($this->user); + get(route('entries.index')) + ->assertSeeInOrder([ + 'id: ', + $student->id, + 'name: ', + $student->full_name(true), + ], false); // The false parameter makes the assertion case-sensitive and allows for HTML tags +}); + +it('has auditions in JS array for select', function () { + // Arrange + $auditions = Audition::factory()->count(5)->create(); + // Act & Assert + actingAs($this->user); + $response = get(route('entries.index')); + foreach ($auditions as $audition) { + $response->assertSeeInOrder([ + 'id: ', + $audition->id, + 'name: ', + $audition->name, + 'minGrade: ', + $audition->minimum_grade, + 'maxGrade: ', + $audition->maximum_grade, + ], false); + } + +}); + +it('shows existing entries in a table', function () { + // Arrange + $students = Student::factory()->count(5)->create(['school_id' => $this->school->id]); + $entries = []; + foreach ($students as $student) { + $entries[] = Entry::factory()->create(['student_id' => $student->id]); + } + foreach ($students as $student) { + Entry::class::factory()->create(['student_id' => $student->id]); + } + // Act & Assert + actingAs($this->user); + $response = get(route('entries.index')); + foreach ($entries as $entry) { + $response-> + assertSeeInOrder([ + 'student->full_name(true), + $entry->student->grade, + $entry->audition->name, + '', + ], false); + } +}); + +it('shows a delete link for entries whose deadline is not past', function () { + // Arrange + $openAudition = Audition::factory()->create(); + $closedAudition = Audition::factory()->closed()->create(); + $student = Student::factory()->create(['school_id' => $this->school->id]); + $pendingEntry = Entry::factory()->create(['audition_id' => $openAudition->id, 'student_id' => $student->id]); + $protectedEntry = Entry::factory()->create(['audition_id' => $closedAudition->id, 'student_id' => $student->id]); + // Act & Assert + actingAs($this->user); + get(route('entries.index')) + ->assertSee(route('entries.destroy', $pendingEntry)) + ->assertDontSee(route('entries.destroy', $protectedEntry)); +}); + +it('shows appropriate flags for entry types when advancement is enabled', function () { + // Arrange + + $student = Student::factory()->create(['school_id' => $this->school->id]); + $auditionOnlyEntry = Entry::factory()->seatingOnly()->create(['student_id' => $student->id]); + $advanceOnlyEntry = Entry::factory()->advanceOnly()->create(['student_id' => $student->id]); + $bothEntry = Entry::factory()->create(['student_id' => $student->id]); + // Act & Assert + actingAs($this->user); + get(route('entries.index')) + ->assertOk() + ->assertSee('is entered for seating. Entry ID '.$bothEntry->id) + ->assertSee('is entered for advancement. Entry ID '.$bothEntry->id) + ->assertSee('is entered for seating. Entry ID '.$auditionOnlyEntry->id) + ->assertSee('is not entered for advancement. Entry ID '.$auditionOnlyEntry->id) + ->assertSee('is entered for advancement. Entry ID '.$advanceOnlyEntry->id) + ->assertSee('is not entered for seating. Entry ID '.$advanceOnlyEntry->id); +}); + +it('accepts a valid entry', function () { + // Arrange + $student = Student::factory()->create(['school_id' => $this->school->id]); + $audition = Audition::factory()->create(); + // Act & Assert + actingAs($this->user); + $response = post(route('entries.store'), [ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + ]); + $response->assertSessionHasNoErrors(); + $response->assertRedirect(route('entries.index')); + $this->assertDatabaseHas('entries', [ + 'student_id' => $student->id, + 'audition_id' => $audition->id, + ]); +}); + +it('deletes an entry', function () { + // Arrange + $student = Student::factory()->create(['school_id' => $this->school->id]); + $audition = Audition::factory()->create(['name' => 'Flute']); + $entry = Entry::factory()->create(['student_id' => $student->id, 'audition_id' => $audition->id]); + // Act + actingAs($this->user); + $response = delete(route('entries.destroy', $entry)); + $response + ->assertSessionHasNoErrors() + ->assertRedirect(route('entries.index')); + // Assert + $this->assertDatabaseMissing('entries', [ + 'id' => $entry->id, + ]); +}); + +it('shows entry type checkboxes only when advancement is enabled', function () { + // Arrange + Settings::set('advanceTo', 'OMEA'); + // Act & Assert + actingAs($this->user); + get(route('entries.index')) + ->assertSee('Enter for'); + Settings::set('advanceTo', null); + get(route('entries.index')) + ->assertDontSee('Enter for'); +});