Tests for JudgingController.php

This commit is contained in:
Matt Young 2025-07-10 15:30:45 -05:00
parent 118a465bb7
commit 3b8c7e6d12
5 changed files with 367 additions and 44 deletions

View File

@ -3,22 +3,18 @@
namespace App\Http\Controllers\Judging; namespace App\Http\Controllers\Judging;
use App\Actions\Tabulation\EnterScore; use App\Actions\Tabulation\EnterScore;
use App\Exceptions\AuditionServiceException;
use App\Exceptions\ScoreEntryException;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Audition; use App\Models\Audition;
use App\Models\Entry; use App\Models\Entry;
use App\Models\JudgeAdvancementVote; use App\Models\JudgeAdvancementVote;
use App\Models\ScoreSheet; use App\Models\ScoreSheet;
use App\Services\AuditionService; use App\Services\AuditionService;
use Exception;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use function compact; use function compact;
use function redirect; use function redirect;
use function url;
class JudgingController extends Controller class JudgingController extends Controller
{ {
@ -40,8 +36,9 @@ class JudgingController extends Controller
public function auditionEntryList(Request $request, Audition $audition) public function auditionEntryList(Request $request, Audition $audition)
{ {
// TODO: Add error message if scoring guide is not set
if ($request->user()->cannot('judge', $audition)) { if ($request->user()->cannot('judge', $audition)) {
return redirect()->route('judging.index')->with('error', 'You are not assigned to judge this audition'); return redirect()->route('judging.index')->with('error', 'You are not assigned to judge that audition');
} }
$entries = Entry::where('audition_id', '=', $audition->id)->orderBy('draw_number')->with('audition')->get(); $entries = Entry::where('audition_id', '=', $audition->id)->orderBy('draw_number')->with('audition')->get();
$subscores = $audition->scoringGuide->subscores()->orderBy('display_order')->get(); $subscores = $audition->scoringGuide->subscores()->orderBy('display_order')->get();
@ -68,6 +65,13 @@ class JudgingController extends Controller
return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error', return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error',
'The requested entry is marked as a no-show. Scores cannot be entered.'); 'The requested entry is marked as a no-show. Scores cannot be entered.');
} }
// Turn away users if the entry is flagged as a failed-prelim
if ($entry->hasFlag('failed_prelim')) {
return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error',
'The requested entry is marked as having failed a prelim. Scores cannot be entered.');
}
$oldSheet = ScoreSheet::where('user_id', Auth::id())->where('entry_id', $entry->id)->value('subscores') ?? null; $oldSheet = ScoreSheet::where('user_id', Auth::id())->where('entry_id', $entry->id)->value('subscores') ?? null;
$oldVote = JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->first(); $oldVote = JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->first();
$oldVote = $oldVote ? $oldVote->vote : 'noVote'; $oldVote = $oldVote ? $oldVote->vote : 'noVote';
@ -78,15 +82,11 @@ class JudgingController extends Controller
public function saveScoreSheet(Request $request, Entry $entry, EnterScore $enterScore) public function saveScoreSheet(Request $request, Entry $entry, EnterScore $enterScore)
{ {
if ($request->user()->cannot('judge', $entry->audition)) { if ($request->user()->cannot('judge', $entry->audition)) {
abort(403, 'You are not assigned to judge this entry'); return redirect()->route('judging.index')->with('error', 'You are not assigned to judge this entry');
} }
// Validate form data // Validate form data
try { $subscores = $entry->audition->subscoreDefinitions;
$subscores = $this->auditionService->getSubscores($entry->audition, 'all');
} catch (AuditionServiceException $e) {
return redirect()->back()->with('error', 'Unable to get subscores - '.$e->getMessage());
}
$validationChecks = []; $validationChecks = [];
foreach ($subscores as $subscore) { foreach ($subscores as $subscore) {
$validationChecks['score'.'.'.$subscore->id] = 'required|integer|max:'.$subscore->max_score; $validationChecks['score'.'.'.$subscore->id] = 'required|integer|max:'.$subscore->max_score;
@ -94,16 +94,13 @@ class JudgingController extends Controller
$validatedData = $request->validate($validationChecks); $validatedData = $request->validate($validationChecks);
// Enter the score // Enter the score
try { /** @noinspection PhpUnhandledExceptionInspection */
$enterScore(Auth::user(), $entry, $validatedData['score']); $enterScore(Auth::user(), $entry, $validatedData['score']);
} catch (ScoreEntryException $e) {
return redirect()->back()->with('error', 'Error saving score - '.$e->getMessage());
}
// Deal with an advancement vote if needed // Deal with an advancement vote if needed
$this->advancementVote($request, $entry); $this->advancementVote($request, $entry);
return redirect('/judging/audition/'.$entry->audition_id)->with('success', return redirect(route('judging.auditionEntryList', $entry->audition))->with('success',
'Entered scores for '.$entry->audition->name.' '.$entry->draw_number); 'Entered scores for '.$entry->audition->name.' '.$entry->draw_number);
} }
@ -111,8 +108,10 @@ class JudgingController extends Controller
public function updateScoreSheet(Request $request, Entry $entry, EnterScore $enterScore) public function updateScoreSheet(Request $request, Entry $entry, EnterScore $enterScore)
{ {
if ($request->user()->cannot('judge', $entry->audition)) { if ($request->user()->cannot('judge', $entry->audition)) {
abort(403, 'You are not assigned to judge this entry'); return redirect()->route('judging.index')->with('error', 'You are not assigned to judge this entry');
} }
// We can't update a scoresheet that doesn't exist
$scoreSheet = ScoreSheet::where('user_id', Auth::id())->where('entry_id', $entry->id)->first(); $scoreSheet = ScoreSheet::where('user_id', Auth::id())->where('entry_id', $entry->id)->first();
if (! $scoreSheet) { if (! $scoreSheet) {
return redirect()->back()->with('error', 'Attempt to edit non existent score sheet'); return redirect()->back()->with('error', 'Attempt to edit non existent score sheet');
@ -120,11 +119,8 @@ class JudgingController extends Controller
Gate::authorize('update', $scoreSheet); Gate::authorize('update', $scoreSheet);
// Validate form data // Validate form data
try {
$subscores = $this->auditionService->getSubscores($entry->audition, 'all'); $subscores = $entry->audition->subscoreDefinitions;
} catch (AuditionServiceException $e) {
return redirect()->back()->with('error', 'Error getting subscores - '.$e->getMessage());
}
$validationChecks = []; $validationChecks = [];
foreach ($subscores as $subscore) { foreach ($subscores as $subscore) {
@ -133,38 +129,29 @@ class JudgingController extends Controller
$validatedData = $request->validate($validationChecks); $validatedData = $request->validate($validationChecks);
// Enter the score // Enter the score
try {
$enterScore(Auth::user(), $entry, $validatedData['score'], $scoreSheet); $enterScore(Auth::user(), $entry, $validatedData['score'], $scoreSheet);
} catch (ScoreEntryException $e) {
return redirect()->back()->with('error', 'Error updating score - '.$e->getMessage());
}
$this->advancementVote($request, $entry); $this->advancementVote($request, $entry);
return redirect('/judging/audition/'.$entry->audition_id)->with('success', return redirect(route('judging.auditionEntryList', $entry->audition))->with('success',
'Updated scores for '.$entry->audition->name.' '.$entry->draw_number); 'Updated scores for '.$entry->audition->name.' '.$entry->draw_number);
} }
protected function advancementVote(Request $request, Entry $entry) protected function advancementVote(Request $request, Entry $entry)
{ {
if ($request->user()->cannot('judge', $entry->audition)) {
abort(403, 'You are not assigned to judge this entry');
}
if ($entry->for_advancement and auditionSetting('advanceTo')) { if ($entry->for_advancement and auditionSetting('advanceTo')) {
$request->validate([ $request->validate([
'advancement-vote' => ['required', 'in:yes,no,dq'], 'advancement-vote' => ['required', 'in:yes,no,dq'],
]); ]);
try {
JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->delete(); JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->delete();
JudgeAdvancementVote::create([ JudgeAdvancementVote::create([
'user_id' => Auth::user()->id, 'user_id' => Auth::user()->id,
'entry_id' => $entry->id, 'entry_id' => $entry->id,
'vote' => $request->input('advancement-vote'), 'vote' => $request->input('advancement-vote'),
]); ]);
} catch (Exception) {
return redirect(url()->previous())->with('error', 'Error saving advancement vote');
}
} }
return null; return null;

View File

@ -55,6 +55,29 @@ class Audition extends Model
return $this->belongsTo(ScoringGuide::class); return $this->belongsTo(ScoringGuide::class);
} }
public function subscoreDefinitions()
{
// TODO: Consider cache. Look at how often this is called.
return $this->hasManyThrough(
SubscoreDefinition::class, // Final related model
ScoringGuide::class, // Intermediate model
'id', // Foreign key on ScoringGuide table (primary key)
'scoring_guide_id', // Foreign key on SubscoreDefinition table
'scoring_guide_id', // Foreign key on Audition table (local key)
'id' // Local key on ScoringGuide table (primary key)
);
}
public function getSeatingSubscores()
{
return $this->subscoreDefinitions()->where('for_seating', '1')->orderBy('display_order')->get();
}
public function getAdvancementSubscores()
{
return $this->subscoreDefinitions()->where('for_advance', '1')->orderBy('display_order')->get();
}
public function bonusScore(): BelongsToMany public function bonusScore(): BelongsToMany
{ {
return $this->belongsToMany(BonusScoreDefinition::class, 'bonus_score_audition_assignment'); return $this->belongsToMany(BonusScoreDefinition::class, 'bonus_score_audition_assignment');

View File

@ -11,7 +11,7 @@
<ul class="mt-.5 max-w-2xl text-sm leading-6 text-gray-500"> <ul class="mt-.5 max-w-2xl text-sm leading-6 text-gray-500">
<li>All Scores must be complete</li> <li>All Scores must be complete</li>
<li>You may enter zero</li> <li>You may enter zero</li>
<li>Whole numbrers only</li> <li>Whole numbers only</li>
</ul> </ul>
</x-slot:subheading> </x-slot:subheading>
</x-card.heading> </x-card.heading>

View File

@ -0,0 +1,284 @@
<?php
use App\Models\Audition;
use App\Models\BonusScoreDefinition;
use App\Models\Entry;
use App\Models\JudgeAdvancementVote;
use App\Models\Room;
use App\Models\ScoreSheet;
use App\Models\ScoringGuide;
use App\Models\SubscoreDefinition;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
describe('JudgingController::index', function () {
it('denies access to non-judges', function () {
actAsNormal();
$response = $this->get(route('judging.index'));
$response->assertRedirect(route('dashboard'));
$response->assertSessionHas('error', 'You are not assigned to judge.');
});
it('shows a dashboard showing rooms and bonus scores the user is assigned to judge', function () {
$judge = User::factory()->create();
$room = Room::factory()->create();
$room->judges()->attach($judge);
$bonusScoreDefinition = BonusScoreDefinition::factory()->create();
$bonusScoreDefinition->judges()->attach($judge);
$response = $this->actingAs($judge)->get(route('judging.index'));
$response->assertOk();
$response->assertSee($room->name);
$response->assertSee($bonusScoreDefinition->name);
});
});
describe('JudgingController::auditionEntryList', function () {
it('denies access to non-judges', function () {
actAsNormal();
$audition = Audition::factory()->create();
$response = $this->get(route('judging.auditionEntryList', $audition->id));
$response->assertRedirect(route('dashboard'));
$response->assertSessionHas('error', 'You are not assigned to judge.');
});
it('denies access if were not assigned to the auditions room', function () {
$judge = User::factory()->create();
$room = Room::factory()->create();
$otherRoom = Room::factory()->create();
$otherRoom->judges()->attach($judge);
$audition = Audition::factory()->create(['room_id' => $room->id]);
$response = $this->actingAs($judge)->get(route('judging.auditionEntryList', $audition->id));
$response->assertRedirect(route('judging.index'));
$response->assertSessionHas('error', 'You are not assigned to judge that audition');
});
it('gives us an entry list from which we may select an entry to score', function () {
$scoringGuide = ScoringGuide::factory()->create();
$judge = User::factory()->create();
$room = Room::factory()->create();
$room->judges()->attach($judge);
$audition = Audition::factory()->create(['room_id' => $room->id, 'scoring_guide_id' => $scoringGuide->id]);
$entries = Entry::factory()->count(5)->forAudition($audition)->create();
$response = $this->actingAs($judge)->get(route('judging.auditionEntryList', $audition->id));
$response->assertOk();
$response->assertViewIs('judging.audition_entry_list');
$response->assertViewHas('audition', $audition);
$response->assertViewHas('entries', $entries);
foreach ($entries as $entry) {
$response->assertSee($entry->audition->name.' '.$entry->draw_number);
$response->assertDontSee($entry->student->full_name());
$response->assertDontSee($entry->student->full_name(true));
}
foreach (SubscoreDefinition::all() as $subscoreDefinition) {
$response->assertSee($subscoreDefinition->name);
}
});
});
describe('JudgingController::entryScoreSheet', function () {
it('denies access to non-judges', function () {
actAsNormal();
$entry = Entry::factory()->create();
$response = $this->get(route('judging.entryScoreSheet', $entry));
$response->assertRedirect(route('dashboard'));
$response->assertSessionHas('error', 'You are not assigned to judge.');
});
it('denies access if were not assigned to the auditions room', function () {
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$entry = Entry::factory()->create();
$this->actingAs($judge);
$response = $this->get(route('judging.entryScoreSheet', $entry));
$response->assertRedirect(route('judging.index'));
$response->assertSessionHas('error', 'You are not assigned to judge this entry');
});
it('denies access if the audition is published', function () {
$room = Room::factory()->create();
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$audition = Audition::factory()->create(['room_id' => $room->id]);
$entry = Entry::factory()->forAudition($audition)->create();
$audition->addFlag('seats_published');
$this->actingAs($judge);
$response = $this->get(route('judging.entryScoreSheet', $entry));
$response->assertRedirect(route('judging.auditionEntryList', $audition));
$response->assertSessionHas('error', 'Scores for entries in published auditions cannot be modified');
});
it('denies access if the entry is flagged as a no-show', function () {
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$audition = Audition::factory()->create(['room_id' => $room->id]);
$entry = Entry::factory()->forAudition($audition)->create();
$entry->addFlag('no_show');
$this->actingAs($judge);
$response = $this->get(route('judging.entryScoreSheet', $entry));
$response->assertRedirect(route('judging.auditionEntryList', $audition));
$response->assertSessionHas('error', 'The requested entry is marked as a no-show. Scores cannot be entered.');
});
it('denies access if the entry is flagged as a failed-prelim', function () {
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$audition = Audition::factory()->create(['room_id' => $room->id]);
$entry = Entry::factory()->forAudition($audition)->create();
$entry->addFlag('failed_prelim');
$this->actingAs($judge);
$response = $this->get(route('judging.entryScoreSheet', $entry));
$response->assertRedirect(route('judging.auditionEntryList', $audition));
$response->assertSessionHas('error',
'The requested entry is marked as having failed a prelim. Scores cannot be entered.');
});
it('gives us a form to enter a score for an entry', function () {
$scoringGuide = ScoringGuide::factory()->create();
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$audition = Audition::factory()->create(['room_id' => $room->id, 'scoring_guide_id' => $scoringGuide->id]);
$entry = Entry::factory()->forAudition($audition)->create();
$this->actingAs($judge);
$response = $this->get(route('judging.entryScoreSheet', $entry));
$response->assertOk();
$response->assertViewIs('judging.entry_score_sheet');
$response->assertDontSee($entry->student->full_name());
foreach (SubscoreDefinition::all() as $subscoreDefinition) {
$response->assertSee($subscoreDefinition->name);
$response->assertSee('score['.$subscoreDefinition->id.']');
$response->assertSee('max: '.$subscoreDefinition->maximum_score);
}
});
});
describe('JudgingController::saveScoreSheet', function () {
it('denies access to non-judges', function () {
actAsNormal();
$audition = Audition::factory()->create();
$entry = Entry::factory()->forAudition($audition)->create();
$response = $this->post(route('judging.saveScoreSheet', $entry));
$response->assertRedirect(route('dashboard'));
$response->assertSessionHas('error', 'You are not assigned to judge.');
});
it('denies access to judges not assigned to the audition', function () {
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$audition = Audition::factory()->create();
$entry = Entry::factory()->forAudition($audition)->create();
$this->actingAs($judge);
$response = $this->post(route('judging.saveScoreSheet', $entry));
$response->assertRedirect(route('judging.index'));
$response->assertSessionHas('error', 'You are not assigned to judge this entry');
});
it('saves a score sheet', function () {
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$scoringGuide = ScoringGuide::factory()->create();
$audition = Audition::factory()->create(['room_id' => $room->id, 'scoring_guide_id' => $scoringGuide->id]);
$subscoreIds = SubscoreDefinition::all()->pluck('id');
$entry = Entry::factory()->forAudition($audition)->create();
$submitData = [
'score' => [
$subscoreIds[0] => 10,
$subscoreIds[1] => 20,
$subscoreIds[2] => 30,
$subscoreIds[3] => 40,
$subscoreIds[4] => 50,
],
'advancement-vote' => 'yes',
];
$this->actingAs($judge);
$response = $this->post(route('judging.saveScoreSheet', $entry), $submitData);
$response->assertRedirect(route('judging.auditionEntryList', $audition));
$response->assertSessionHas('success');
expect(ScoreSheet::where('entry_id', $entry->id)->first())->toBeInstanceOf(ScoreSheet::class)
->and(JudgeAdvancementVote::where('entry_id',
$entry->id)->first())->toBeInstanceOf(JudgeAdvancementVote::class)
->and(JudgeAdvancementVote::first()->vote)->toBe('yes');
});
});
describe('JudgingController::updateScoreSheet', function () {
it('denies access to non-judges', function () {
actAsNormal();
$audition = Audition::factory()->create();
$entry = Entry::factory()->forAudition($audition)->create();
$response = $this->patch(route('judging.updateScoreSheet', $entry));
$response->assertRedirect(route('dashboard'));
$response->assertSessionHas('error', 'You are not assigned to judge.');
});
it('denies access to judges not assigned to the audition', function () {
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$audition = Audition::factory()->create();
$entry = Entry::factory()->forAudition($audition)->create();
$this->actingAs($judge);
$response = $this->patch(route('judging.updateScoreSheet', $entry));
$response->assertRedirect(route('judging.index'));
$response->assertSessionHas('error', 'You are not assigned to judge this entry');
});
it('will not update a non-existent score sheet', function () {
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$audition = Audition::factory()->create(['room_id' => $room->id]);
$entry = Entry::factory()->forAudition($audition)->create();
$this->actingAs($judge);
$response = $this->patch(route('judging.updateScoreSheet', $entry));
$response->assertRedirect()->assertSessionHas('error', 'Attempt to edit non existent score sheet');
});
it('will update a score sheet', function () {
$room = Room::factory()->create();
$judge = User::factory()->create();
$room->addJudge($judge);
$scoringGuide = ScoringGuide::factory()->create();
$audition = Audition::factory()->create(['room_id' => $room->id, 'scoring_guide_id' => $scoringGuide->id]);
$subscoreIds = SubscoreDefinition::all()->pluck('id');
$entry = Entry::factory()->forAudition($audition)->create();
$submitData = [
'score' => [
$subscoreIds[0] => 10,
$subscoreIds[1] => 20,
$subscoreIds[2] => 30,
$subscoreIds[3] => 40,
$subscoreIds[4] => 50,
],
'advancement-vote' => 'yes',
];
$this->actingAs($judge);
$this->post(route('judging.saveScoreSheet', $entry), $submitData);
$newSubmitData = [
'score' => [
$subscoreIds[0] => 5,
$subscoreIds[1] => 15,
$subscoreIds[2] => 25,
$subscoreIds[3] => 35,
$subscoreIds[4] => 45,
],
'advancement-vote' => 'no',
];
$response = $this->patch(route('judging.updateScoreSheet', $entry), $newSubmitData);
$response->assertRedirect(route('judging.auditionEntryList', $audition))
->assertSessionHas('success');
expect(ScoreSheet::count())->toEqual(1);
$ss = ScoreSheet::first();
expect($ss->getSubscore($subscoreIds[0]))->toEqual(5);
expect($ss->getSubscore($subscoreIds[1]))->toEqual(15);
expect($ss->getSubscore($subscoreIds[2]))->toEqual(25);
expect($ss->getSubscore($subscoreIds[3]))->toEqual(35);
expect($ss->getSubscore($subscoreIds[4]))->toEqual(45);
expect(JudgeAdvancementVote::count())->toEqual(1);
$vote = JudgeAdvancementVote::first();
expect($vote->vote)->toEqual('no');
});
});

View File

@ -4,8 +4,10 @@ use App\Models\Audition;
use App\Models\Ensemble; use App\Models\Ensemble;
use App\Models\Entry; use App\Models\Entry;
use App\Models\Room; use App\Models\Room;
use App\Models\ScoringGuide;
use App\Models\Seat; use App\Models\Seat;
use App\Models\SeatingLimit; use App\Models\SeatingLimit;
use App\Models\SubscoreDefinition;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class); uses(RefreshDatabase::class);
@ -62,6 +64,33 @@ it('can return its scoring guide if one is set', function () {
expect($this->audition->scoringGuide)->toBeInstanceOf(App\Models\ScoringGuide::class); expect($this->audition->scoringGuide)->toBeInstanceOf(App\Models\ScoringGuide::class);
}); });
it('can return the subscore definitions for its scoring guide if one is set', function () {
expect($this->audition->scoringGuide)->toBeNull();
$guide = ScoringGuide::factory()->create();
$subscores = SubscoreDefinition::orderBy('display_order')->get();
$n = 1;
foreach ($subscores as $subscore) {
$subscore->update(['display_order' => $n]);
$n++;
}
$subscores[0]->update(['for_seating' => 0]);
$subscores[4]->update(['for_advance' => 0]);
$this->audition->scoringGuide()->associate($guide);
$this->audition->save();
expect($this->audition->scoringGuide)->toBeInstanceOf(ScoringGuide::class)
->and($this->audition->getSeatingSubscores()->contains('id', $subscores[0]->id))->toBeFalse()
->and($this->audition->getSeatingSubscores()->contains('id', $subscores[1]->id))->toBeTrue()
->and($this->audition->getSeatingSubscores()->contains('id', $subscores[2]->id))->toBeTrue()
->and($this->audition->getSeatingSubscores()->contains('id', $subscores[3]->id))->toBeTrue()
->and($this->audition->getSeatingSubscores()->contains('id', $subscores[4]->id))->toBeTrue()
->and($this->audition->getAdvancementSubscores()->contains('id', $subscores[4]->id))->toBeFalse()
->and($this->audition->getAdvancementSubscores()->contains('id', $subscores[3]->id))->toBeTrue()
->and($this->audition->getAdvancementSubscores()->contains('id', $subscores[2]->id))->toBeTrue()
->and($this->audition->getAdvancementSubscores()->contains('id', $subscores[1]->id))->toBeTrue()
->and($this->audition->getAdvancementSubscores()->contains('id', $subscores[0]->id))->toBeTrue();
});
it('can return its bonus score definition if one is set', function () { it('can return its bonus score definition if one is set', function () {
expect($this->audition->bonusScore()->count())->toBe(0); expect($this->audition->bonusScore()->count())->toBe(0);
$definition = App\Models\BonusScoreDefinition::factory()->create(); $definition = App\Models\BonusScoreDefinition::factory()->create();