Files
suivicpt/tests/Feature/SuiviTest.php
T
jeremy bayseandClaude Sonnet 5 613220f43a Add budget progress gauge inside mobile category buttons
Each depense/epargne category button on the mobile quick-entry form
now shows a fill bar plus percentage of its monthly budget already
tracked, so the user sees at a glance which categories are close to
their limit while picking one. Categories without a budget defined
in Plan show no gauge (avoids a misleading 0%). Capped at 100% —
overspend detail already lives on the dashboard's "Manque" column.

3 new Pest tests for the percentage calculation (normal, capped,
no-budget cases). 61/61 tests pass, Pint clean, verified on mobile
against production data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-26 12:48:29 +02:00

249 lines
10 KiB
PHP

<?php
use App\Livewire\Suivi\Index;
use App\Models\User;
use Livewire\Livewire;
it('records a transaction for the current household', function (): void {
$user = User::factory()->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);
});