create(); $resolver = app(MacroResolutionService::class); expect($resolver->resolveAssignmentsForPart($service, 'song'))->toBeEmpty(); }); test('returns global assignments when no override', function () { $service = Service::factory()->create(); $macro = Macro::factory()->create(); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'all_slides', 'order' => 0]); $resolver = app(MacroResolutionService::class); $resolved = $resolver->resolveAssignmentsForPart($service, 'song'); expect($resolved)->toHaveCount(1); expect($resolved->first()->macro->id)->toBe($macro->id); }); test('override wins over globals', function () { $service = Service::factory()->create(); $macroA = Macro::factory()->create(['name' => 'Global']); $macroB = Macro::factory()->create(['name' => 'Override']); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macroA->id, 'position' => 'all_slides', 'order' => 0]); ServiceMacroOverride::create(['service_id' => $service->id, 'part_type' => 'song']); ServiceMacroAssignment::create(['service_id' => $service->id, 'part_type' => 'song', 'macro_id' => $macroB->id, 'position' => 'all_slides', 'order' => 0]); $resolver = app(MacroResolutionService::class); $resolved = $resolver->resolveAssignmentsForPart($service, 'song'); expect($resolved)->toHaveCount(1); expect($resolved->first()->macro->name)->toBe('Override'); }); test('hidden macros are filtered out', function () { $service = Service::factory()->create(); $macro = Macro::factory()->create(['hidden_at' => now()]); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'all_slides', 'order' => 0]); $resolver = app(MacroResolutionService::class); expect($resolver->resolveAssignmentsForPart($service, 'song'))->toBeEmpty(); }); test('macrosForSlide with all_slides matches every slide', function () { $service = Service::factory()->create(); $macro = Macro::factory()->create(); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'all_slides', 'order' => 0]); $resolver = app(MacroResolutionService::class); $result = $resolver->macrosForSlide($service, 'song', ['index' => 0, 'total' => 3, 'label_id' => null]); expect($result)->toHaveCount(1); expect($result[0]['uuid'])->toBe($macro->uuid); }); test('macrosForSlide with first_slide only matches index 0', function () { $service = Service::factory()->create(); $macro = Macro::factory()->create(); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'first_slide', 'order' => 0]); $resolver = app(MacroResolutionService::class); expect($resolver->macrosForSlide($service, 'song', ['index' => 0, 'total' => 3, 'label_id' => null]))->toHaveCount(1); expect($resolver->macrosForSlide($service, 'song', ['index' => 1, 'total' => 3, 'label_id' => null]))->toHaveCount(0); }); test('macrosForSlide stacking — multiple matching assignments produce multiple macros', function () { $service = Service::factory()->create(); $macro1 = Macro::factory()->create(); $macro2 = Macro::factory()->create(); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro1->id, 'position' => 'all_slides', 'order' => 0]); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro2->id, 'position' => 'first_slide', 'order' => 1]); $resolver = app(MacroResolutionService::class); $result = $resolver->macrosForSlide($service, 'song', ['index' => 0, 'total' => 3, 'label_id' => null]); expect($result)->toHaveCount(2); }); test('macrosForSlide with by_label matches only matching label_id', function () { $service = Service::factory()->create(); $label = Label::factory()->create(); $macro = Macro::factory()->create(); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'by_label', 'label_id' => $label->id, 'order' => 0]); $resolver = app(MacroResolutionService::class); expect($resolver->macrosForSlide($service, 'song', ['index' => 0, 'total' => 3, 'label_id' => $label->id]))->toHaveCount(1); expect($resolver->macrosForSlide($service, 'song', ['index' => 0, 'total' => 3, 'label_id' => 9999]))->toHaveCount(0); }); test('countAssignmentsForPart returns correct count', function () { $service = Service::factory()->create(); $macro = Macro::factory()->create(); MacroAssignment::create(['part_type' => 'song', 'macro_id' => $macro->id, 'position' => 'all_slides', 'order' => 0]); MacroAssignment::create(['part_type' => 'sermon', 'macro_id' => $macro->id, 'position' => 'all_slides', 'order' => 0]); $resolver = app(MacroResolutionService::class); expect($resolver->countAssignmentsForPart($service, 'song'))->toBe(1); expect($resolver->countAssignmentsForPart($service, 'sermon'))->toBe(1); expect($resolver->countAssignmentsForPart($service, 'information'))->toBe(0); });