create(); $category = $user->currentHousehold->categories()->create(['name' => 'Salaire', 'type' => 'revenu', 'position' => 1]); Livewire::actingAs($user) ->test(Index::class) ->set('categoryId', $category->id) ->set('amount', '2900') ->set('details', 'Paie juillet') ->call('addTransaction') ->assertHasNoErrors(); expect($user->currentHousehold->transactions()->count())->toBe(1); expect((float) $user->currentHousehold->transactions()->first()->amount)->toBe(2900.0); }); it('computes a running balance that subtracts depenses and adds revenus', function (): void { $user = User::factory()->create(); $household = $user->currentHousehold; $revenu = $household->categories()->create(['name' => 'Salaire', 'type' => 'revenu', 'position' => 1]); $depense = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $household->transactions()->create([ 'category_id' => $revenu->id, 'user_id' => $user->id, 'date' => '2026-01-01', 'date_effective' => '2026-01-01', 'amount' => 2900, ]); $household->transactions()->create([ 'category_id' => $depense->id, 'user_id' => $user->id, 'date' => '2026-01-02', 'date_effective' => '2026-01-02', 'amount' => 1200, ]); $component = Livewire::actingAs($user)->test(Index::class); $running = $component->viewData('running'); $transactions = $component->viewData('transactions'); $ordered = $transactions->sortBy('date')->values(); expect($running[$ordered[0]->id])->toBe(2900.0); expect($running[$ordered[1]->id])->toBe(1700.0); }); it('subtracts epargne contributions from the running balance', function (): void { $user = User::factory()->create(); $household = $user->currentHousehold; $revenu = $household->categories()->create(['name' => 'Salaire', 'type' => 'revenu', 'position' => 1]); $epargne = $household->categories()->create(['name' => 'Vacances', 'type' => 'epargne', 'position' => 1]); $household->transactions()->create([ 'category_id' => $revenu->id, 'user_id' => $user->id, 'date' => '2026-01-01', 'date_effective' => '2026-01-01', 'amount' => 2900, ]); $household->transactions()->create([ 'category_id' => $epargne->id, 'user_id' => $user->id, 'date' => '2026-01-02', 'date_effective' => '2026-01-02', 'amount' => 200, ]); $component = Livewire::actingAs($user)->test(Index::class); $running = $component->viewData('running'); $transactions = $component->viewData('transactions'); $ordered = $transactions->sortBy('date')->values(); expect($running[$ordered[0]->id])->toBe(2900.0); expect($running[$ordered[1]->id])->toBe(2700.0); }); it('dispatches a transaction-added event after successfully adding a transaction', function (): void { $user = User::factory()->create(); $category = $user->currentHousehold->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); Livewire::actingAs($user) ->test(Index::class) ->set('categoryId', $category->id) ->set('amount', '1200') ->call('addTransaction') ->assertDispatched('transaction-added'); }); it('resets the form to defaults after adding a transaction', function (): void { $user = User::factory()->create(); $category = $user->currentHousehold->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $component = Livewire::actingAs($user) ->test(Index::class) ->set('categoryId', $category->id) ->set('amount', '1200') ->set('details', 'Août') ->call('addTransaction'); expect($component->get('categoryId'))->toBeNull(); expect($component->get('amount'))->toBe(''); expect($component->get('details'))->toBe(''); }); it('lists active recurring transactions due this month as pending', function (): void { $this->travelTo(now()->setDate(2026, 3, 10)); $user = User::factory()->create(); $household = $user->currentHousehold; $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $due = $household->recurringTransactions()->create([ 'category_id' => $category->id, 'name' => 'Loyer', 'amount' => 1200, 'day_of_month' => 5, ]); $notYetDue = $household->recurringTransactions()->create([ 'category_id' => $category->id, 'name' => 'Internet', 'amount' => 40, 'day_of_month' => 20, ]); $inactive = $household->recurringTransactions()->create([ 'category_id' => $category->id, 'name' => 'Ancien', 'amount' => 10, 'day_of_month' => 1, 'active' => false, ]); $component = Livewire::actingAs($user)->test(Index::class); $pending = $component->viewData('pendingRecurrences'); expect($pending->pluck('id'))->toContain($due->id); expect($pending->pluck('id'))->not->toContain($notYetDue->id); expect($pending->pluck('id'))->not->toContain($inactive->id); }); it('confirming a recurring transaction creates a transaction and stops it reappearing', function (): void { $this->travelTo(now()->setDate(2026, 3, 10)); $user = User::factory()->create(); $household = $user->currentHousehold; $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $recurring = $household->recurringTransactions()->create([ 'category_id' => $category->id, 'name' => 'Loyer', 'amount' => 1200, 'day_of_month' => 5, ]); Livewire::actingAs($user) ->test(Index::class) ->call('confirmRecurrence', $recurring->id, '1250') ->assertDispatched('transaction-added'); $transaction = $household->transactions()->where('recurring_transaction_id', $recurring->id)->first(); expect($transaction)->not->toBeNull(); expect((float) $transaction->amount)->toBe(1250.0); expect($transaction->details)->toBe('Loyer'); $component = Livewire::actingAs($user)->test(Index::class); expect($component->viewData('pendingRecurrences'))->toHaveCount(0); }); it('dismissing a recurring transaction hides it without creating a transaction', function (): void { $this->travelTo(now()->setDate(2026, 3, 10)); $user = User::factory()->create(); $household = $user->currentHousehold; $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $recurring = $household->recurringTransactions()->create([ 'category_id' => $category->id, 'name' => 'Loyer', 'amount' => 1200, 'day_of_month' => 5, ]); Livewire::actingAs($user)->test(Index::class)->call('dismissRecurrence', $recurring->id); expect($household->transactions()->count())->toBe(0); $component = Livewire::actingAs($user)->test(Index::class); expect($component->viewData('pendingRecurrences'))->toHaveCount(0); }); it('shows the recurrence again the following month after being confirmed', function (): void { $this->travelTo(now()->setDate(2026, 3, 10)); $user = User::factory()->create(); $household = $user->currentHousehold; $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $recurring = $household->recurringTransactions()->create([ 'category_id' => $category->id, 'name' => 'Loyer', 'amount' => 1200, 'day_of_month' => 5, ]); Livewire::actingAs($user)->test(Index::class)->call('confirmRecurrence', $recurring->id, '1200'); $this->travelTo(now()->setDate(2026, 4, 10)); $component = Livewire::actingAs($user)->test(Index::class); expect($component->viewData('pendingRecurrences')->pluck('id'))->toContain($recurring->id); }); it('computes the percentage of budget already tracked for a category this month', function (): void { $this->travelTo(now()->setDate(2026, 3, 10)); $user = User::factory()->create(); $household = $user->currentHousehold; $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $category->budgetLines()->create(['household_id' => $household->id, 'year' => 2026, 'month' => null, 'amount' => 1000]); $household->transactions()->create([ 'category_id' => $category->id, 'user_id' => $user->id, 'date' => '2026-03-05', 'date_effective' => '2026-03-05', 'amount' => 600, ]); $component = Livewire::actingAs($user)->test(Index::class); expect($component->viewData('categoryProgress')[$category->id])->toBe(60.0); }); it('caps the category progress percentage at 100', function (): void { $this->travelTo(now()->setDate(2026, 3, 10)); $user = User::factory()->create(); $household = $user->currentHousehold; $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $category->budgetLines()->create(['household_id' => $household->id, 'year' => 2026, 'month' => null, 'amount' => 1000]); $household->transactions()->create([ 'category_id' => $category->id, 'user_id' => $user->id, 'date' => '2026-03-05', 'date_effective' => '2026-03-05', 'amount' => 1500, ]); $component = Livewire::actingAs($user)->test(Index::class); expect($component->viewData('categoryProgress')[$category->id])->toBe(100); }); it('returns no progress for a category without a budget defined', function (): void { $this->travelTo(now()->setDate(2026, 3, 10)); $user = User::factory()->create(); $household = $user->currentHousehold; $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); $component = Livewire::actingAs($user)->test(Index::class); expect($component->viewData('categoryProgress')[$category->id])->toBeNull(); }); it('does not show another household transactions', function (): void { $ownerA = User::factory()->create(); $ownerB = User::factory()->create(); $categoryA = $ownerA->currentHousehold->categories()->create(['name' => 'Salaire', 'type' => 'revenu', 'position' => 1]); $ownerA->currentHousehold->transactions()->create([ 'category_id' => $categoryA->id, 'user_id' => $ownerA->id, 'date' => now(), 'date_effective' => now(), 'amount' => 1000, ]); $component = Livewire::actingAs($ownerB)->test(Index::class); expect($component->viewData('transactions'))->toHaveCount(0); });