Initial commit: SuiviCPT personal finance tracker
Laravel 11 + Livewire 3/Volt + Alpine.js + Tailwind app for household budget planning, transaction tracking, and financial dashboards. - Multi-tenant households with member invites - Plan: monthly/yearly budget per category with overrides - Suivi: transaction log with running balance (desktop + mobile quick-entry) - Dashboard: budget vs tracked breakdown + Chart.js donuts - Flux: Sankey diagram of income allocation - WCAG 2.1 AA color tokens, dark mode, accessible tables/forms - 50 Pest tests Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
<?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('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);
|
||||
});
|
||||
Reference in New Issue
Block a user