refactor: move tier data loading to render method, adjust form return types, and add feature tests for tier management.
This commit is contained in:
parent
53554a0260
commit
c8fde13435
@ -59,23 +59,23 @@ public function setTier(Tier $tier): void
|
||||
$this->max_spending = $tier->max_spending ? formatCurrencyNumber($tier->max_spending) : null;
|
||||
}
|
||||
|
||||
public function store(): Tier
|
||||
public function store(): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
return DB::transaction(function () {
|
||||
return Tier::create($this->prepareSavedData());
|
||||
DB::transaction(function () {
|
||||
Tier::create($this->prepareSavedData());
|
||||
});
|
||||
}
|
||||
|
||||
public function update(): Tier
|
||||
public function update(): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
return DB::transaction(function () {
|
||||
DB::transaction(function () {
|
||||
$this->tier->update($this->prepareSavedData());
|
||||
|
||||
return $this->tier;
|
||||
$this->tier;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -25,37 +25,12 @@ class Tier extends Component
|
||||
{
|
||||
use WithAuthorization, WithCloseModal, WithConfirmation, WithSubscribeNotification, WithToast, WithUpdatedData;
|
||||
|
||||
public array $tiers = [];
|
||||
|
||||
public TierForm $form;
|
||||
|
||||
public string $method = 'create';
|
||||
|
||||
public string $modalTitle = '';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->loadTiers();
|
||||
}
|
||||
|
||||
#[On('data:tiersUpdated')]
|
||||
public function loadTiers(): void
|
||||
{
|
||||
$this->tiers = TierModel::withCount('memberships')
|
||||
->orderBy('min_spending', 'asc')
|
||||
->get()
|
||||
->map(fn (TierModel $tier) => [
|
||||
'id' => $tier->id,
|
||||
'hash' => $tier->hash,
|
||||
'name' => $tier->name,
|
||||
'min_spending' => formatCurrencyNumber($tier->min_spending, 'Rp'),
|
||||
'max_spending' => $tier->max_spending ? formatCurrencyNumber($tier->max_spending, 'Rp') : 'Tak terbatas',
|
||||
'total_members' => formatCurrencyNumber($tier->memberships_count),
|
||||
'top_member' => $this->getTopMemberStats($tier->id),
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getTopMemberStats($tierId): ?array
|
||||
{
|
||||
$topMembership = Membership::where('tier_id', $tierId)
|
||||
@ -107,9 +82,6 @@ public function create(): void
|
||||
// Reorder the entire tier chain to ensure proper max_spending
|
||||
TierModel::reorderTierChain();
|
||||
|
||||
// Reload tiers to reflect the changes
|
||||
$this->loadTiers();
|
||||
|
||||
$this->toast('Tier berhasil ditambahkan.');
|
||||
|
||||
Flux::modals()->close();
|
||||
@ -129,9 +101,6 @@ public function update(): void
|
||||
TierModel::reorderTierChain();
|
||||
}
|
||||
|
||||
// Reload tiers to reflect any changes from reordering
|
||||
$this->loadTiers();
|
||||
|
||||
$this->toast('Tier berhasil diperbarui.');
|
||||
|
||||
Flux::modals()->close();
|
||||
@ -154,9 +123,6 @@ public function delete(TierModel $tier): void
|
||||
// Reorder tier chain after deletion to ensure proper max_spending
|
||||
TierModel::reorderTierChain();
|
||||
|
||||
// Reload tiers to reflect the changes
|
||||
$this->loadTiers();
|
||||
|
||||
$this->toast('Tier berhasil dihapus.');
|
||||
|
||||
Flux::modals()->close();
|
||||
@ -176,8 +142,23 @@ public function openModal(string $method, string $modalTitle, ?string $id = null
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$tiers = TierModel::withCount('memberships')
|
||||
->orderBy('min_spending', 'asc')
|
||||
->get()
|
||||
->map(fn (TierModel $tier) => [
|
||||
'id' => $tier->id,
|
||||
'hash' => $tier->hash,
|
||||
'name' => $tier->name,
|
||||
'min_spending' => formatCurrencyNumber($tier->min_spending, 'Rp'),
|
||||
'max_spending' => $tier->max_spending ? formatCurrencyNumber($tier->max_spending, 'Rp') : 'Tak terbatas',
|
||||
'total_members' => formatCurrencyNumber($tier->memberships_count),
|
||||
'top_member' => $this->getTopMemberStats($tier->id),
|
||||
])
|
||||
->toArray();
|
||||
|
||||
return view('livewire.studio.loyalty.tiers', [
|
||||
'pageTitle' => 'Tier',
|
||||
'tiers' => $tiers,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
158
tests/Feature/Studio/Loyalty/TierTest.php
Normal file
158
tests/Feature/Studio/Loyalty/TierTest.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Studio\Loyalty\Tier;
|
||||
use App\Models\Tier as TierModel;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->setupUser();
|
||||
|
||||
$role = Role::firstOrCreate(['name' => 'Owner']);
|
||||
Permission::firstOrCreate(['name' => 'view tier']);
|
||||
Permission::firstOrCreate(['name' => 'create tier']);
|
||||
Permission::firstOrCreate(['name' => 'update tier']);
|
||||
Permission::firstOrCreate(['name' => 'delete tier']);
|
||||
$role->givePermissionTo(['view tier', 'create tier', 'update tier', 'delete tier']);
|
||||
$this->user->assignRole($role);
|
||||
});
|
||||
|
||||
it('renders the tier index page correctly', function () {
|
||||
$this->actingAs($this->user)
|
||||
->get(route('studio.loyalty.tier.index'))
|
||||
->assertOk()
|
||||
->assertSeeLivewire(Tier::class);
|
||||
});
|
||||
|
||||
it('can open create modal', function () {
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Tier::class)
|
||||
->dispatch('modal:open', method: 'create', modalTitle: 'Tambah Tier')
|
||||
->assertSet('method', 'create')
|
||||
->assertSet('modalTitle', 'Tambah Tier');
|
||||
});
|
||||
|
||||
it('validates required fields on create', function () {
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Tier::class)
|
||||
->call('create')
|
||||
->assertHasErrors(['form.name' => 'required', 'form.min_spending' => 'required']);
|
||||
});
|
||||
|
||||
it('can store a new tier', function () {
|
||||
// Ensure no tiers exist first
|
||||
TierModel::query()->delete();
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Tier::class)
|
||||
->set('form.name', 'Bronze')
|
||||
->set('form.min_spending', '0')
|
||||
->call('create')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$this->assertDatabaseHas('tiers', [
|
||||
'name' => 'Bronze',
|
||||
'min_spending' => 0,
|
||||
]);
|
||||
});
|
||||
|
||||
it('automatically reorders tier chain on create', function () {
|
||||
TierModel::query()->delete();
|
||||
|
||||
// Create first tier manually
|
||||
TierModel::create(['name' => 'Bronze', 'min_spending' => 0]);
|
||||
|
||||
// Create second tier via Livewire
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Tier::class)
|
||||
->set('form.name', 'Silver')
|
||||
->set('form.min_spending', '10.000') // 10,000
|
||||
->call('create')
|
||||
->assertHasNoErrors();
|
||||
|
||||
// First tier should now have max_spending = 9999
|
||||
$bronze = TierModel::where('name', 'Bronze')->first();
|
||||
expect($bronze->max_spending)->toBe(9999);
|
||||
|
||||
// Silver should have null max_spending
|
||||
$silver = TierModel::where('name', 'Silver')->first();
|
||||
expect($silver->max_spending)->toBeNull();
|
||||
});
|
||||
|
||||
it('can open edit modal and load data', function () {
|
||||
$tier = TierModel::factory()->create([
|
||||
'name' => 'Gold',
|
||||
'min_spending' => 50000,
|
||||
'max_spending' => null,
|
||||
]);
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Tier::class)
|
||||
->call('openModal', 'update', 'Ubah Tier', $tier->hash_id)
|
||||
->set('form.tier', $tier)
|
||||
->set('form.name', 'Gold')
|
||||
->set('form.min_spending', formatCurrencyNumber(50000))
|
||||
->assertSet('method', 'update')
|
||||
->assertSet('modalTitle', 'Ubah Tier')
|
||||
->assertSet('form.name', 'Gold')
|
||||
->assertSet('form.min_spending', formatCurrencyNumber(50000));
|
||||
});
|
||||
|
||||
it('can update a tier', function () {
|
||||
$tier = TierModel::factory()->create(['name' => 'Old Name', 'min_spending' => 0]);
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Tier::class)
|
||||
->call('openModal', 'update', 'Ubah Tier', $tier->hash_id)
|
||||
->set('form.tier', $tier)
|
||||
->set('form.name', 'New Name')
|
||||
->set('form.min_spending', '0')
|
||||
->call('update')
|
||||
->assertHasNoErrors();
|
||||
|
||||
$this->assertDatabaseHas('tiers', [
|
||||
'id' => $tier->id,
|
||||
'name' => 'New Name',
|
||||
]);
|
||||
});
|
||||
|
||||
it('can delete the last tier', function () {
|
||||
TierModel::query()->delete();
|
||||
$tier1 = TierModel::create(['name' => 'Bronze', 'min_spending' => 0]);
|
||||
$tier2 = TierModel::create(['name' => 'Silver', 'min_spending' => 10000]);
|
||||
TierModel::reorderTierChain();
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Tier::class)
|
||||
->call('delete', $tier2->id); // Deleting highest tier (Silver)
|
||||
|
||||
$this->assertSoftDeleted('tiers', ['id' => $tier2->id]);
|
||||
|
||||
// Bronze should now be the last tier with null max_spending
|
||||
$tier1->refresh();
|
||||
expect($tier1->max_spending)->toBeNull();
|
||||
});
|
||||
|
||||
it('cannot delete a middle tier', function () {
|
||||
TierModel::query()->delete();
|
||||
$tier1 = TierModel::create(['name' => 'Bronze', 'min_spending' => 0]);
|
||||
$tier2 = TierModel::create(['name' => 'Silver', 'min_spending' => 10000]);
|
||||
$tier3 = TierModel::create(['name' => 'Gold', 'min_spending' => 20000]);
|
||||
TierModel::reorderTierChain();
|
||||
|
||||
Livewire::actingAs($this->user)
|
||||
->test(Tier::class)
|
||||
->call('delete', $tier2->id);
|
||||
|
||||
$this->assertDatabaseHas('tiers', ['id' => $tier2->id]);
|
||||
});
|
||||
|
||||
it('cannot access tier page without permission', function () {
|
||||
$this->user->roles()->detach();
|
||||
$this->user->permissions()->detach();
|
||||
|
||||
$this->actingAs($this->user)
|
||||
->get(route('studio.loyalty.tier.index'))
|
||||
->assertForbidden();
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user