create(['onboarded_at' => now()]); $category = Category::factory()->create([ 'user_id' => $user->id, 'name' => 'Groceries', ]); $page = $this->actingAs($user)->visit('/budgets'); $page->wait(2); // Wait for page to fully load $page->assertSee('Budgets') ->waitForText('Create Budget', 10) ->wait(1) // Extra wait before clicking ->click('Create Budget') ->wait(3) // Wait for dialog to open ->assertSee('Create Budget') ->wait(1) // Wait for form to be ready ->fill('name', 'Monthly Groceries') ->wait(1) ->click('#period-type') ->wait(0.5) ->click('[role="option"]:has-text("Monthly")') ->wait(1) ->click('button:has-text("Select categories")') ->wait(0.5) ->click('[role="option"]:has-text("'.$category->name.'")') ->wait(0.5) ->click('#categories') // Close the categories combobox popover ->wait(1) ->click('button:has-text("Carry Over")') ->wait(0.5) ->click('[role="option"]:has-text("Reset/Pool")') ->wait(1) ->fill('#allocated-amount', '500') ->click('label:has-text("Rollover Type")') // Click elsewhere to blur the amount input ->wait(2) // Wait for state to update ->click('[role="dialog"] button[type="submit"]') ->wait(4) // Wait for form submission ->assertPathBeginsWith('/budgets/') ->wait(2) // Wait for page to update ->waitForText('Monthly Groceries', 15) ->assertSee('Budget Spending') ->assertNoJavascriptErrors(); $budget = Budget::where('user_id', $user->id)->where('name', 'Monthly Groceries')->first(); expect($budget)->not->toBeNull() ->and($budget->categories->pluck('id')->contains($category->id))->toBeTrue(); }); test('user can update budget name', function () { $user = User::factory()->create(['onboarded_at' => now()]); $category = Category::factory()->create(['user_id' => $user->id]); $budget = Budget::factory()->forCategories($category)->create([ 'user_id' => $user->id, 'name' => 'Old Name', ]); $page = $this->actingAs($user)->visit("/budgets/{$budget->id}"); $page->wait(2); // Wait for page to fully load $page->assertSee('Old Name') ->wait(4) ->click('[aria-label="More options"]') ->wait(1) // Wait for dropdown to open ->click('Edit budget') ->wait(3) // Wait for dialog to open ->assertSee('Edit Budget') ->wait(1) // Wait for form to be ready ->fill('name', 'New Budget Name') ->wait(1) ->fill('#allocated-amount', '500') ->wait(2) ->click('button:has-text("Save Changes")') ->waitForText('Saving...', 5) ->waitForEvent('networkidle') ->assertScript("document.querySelector('[role=\"dialog\"]') === null") ->assertPathIs("/budgets/{$budget->id}") ->assertSee('Budget Spending') ->assertSee('New Budget Name') ->assertTitleContains('New Budget Name') ->assertNoJavascriptErrors(); $this->assertDatabaseHas('budgets', [ 'id' => $budget->id, 'name' => 'New Budget Name', ]); }); test('user can delete a budget', function () { $user = User::factory()->create(['onboarded_at' => now()]); $category = Category::factory()->create(['user_id' => $user->id]); $budget = Budget::factory()->forCategories($category)->create([ 'user_id' => $user->id, 'name' => 'Budget to Delete', ]); $page = $this->actingAs($user)->visit("/budgets/{$budget->id}"); $page->wait(2); // Wait for page to fully load $page->assertSee('Budget to Delete') ->wait(4) ->click('[aria-label="More options"]') ->wait(1) // Wait for dropdown to open ->click('Delete budget') ->wait(3) // Wait for dialog to open ->assertSee('Delete Budget') ->assertSee('Are you sure') ->wait(2) ->click('button:has-text("Delete")') ->wait(4) // Wait for deletion ->assertPathIs('/budgets') ->wait(2) // Wait for page to update ->assertDontSee('Budget to Delete') ->assertNoJavascriptErrors(); $this->assertSoftDeleted('budgets', [ 'id' => $budget->id, ]); }); test('budget creation validates required fields', function () { $user = User::factory()->create(['onboarded_at' => now()]); $page = $this->actingAs($user)->visit('/budgets'); $page->wait(2); // Wait for page to fully load $page->assertSee('Budgets') ->waitForText('Create Budget', 10) ->wait(1) // Extra wait before clicking ->click('Create Budget') ->wait(3) // Wait for dialog to open ->assertSee('Create Budget') ->wait(2) // Wait for form to be ready ->assertAttribute('button[type="submit"]:has-text("Create Budget")', 'disabled', '') ->assertNoJavascriptErrors(); }); test('budget shows current period information', function () { $user = User::factory()->create(['onboarded_at' => now()]); $category = Category::factory()->create(['user_id' => $user->id]); $budget = Budget::factory()->forCategories($category)->create([ 'user_id' => $user->id, ]); $page = $this->actingAs($user)->visit("/budgets/{$budget->id}"); $page->assertSee($budget->name) ->assertSee('Tracking') ->assertNoJavascriptErrors(); }); test('user can navigate back to budgets list from budget detail', function () { $user = User::factory()->create(['onboarded_at' => now()]); $category = Category::factory()->create(['user_id' => $user->id]); $budget = Budget::factory()->forCategories($category)->create([ 'user_id' => $user->id, ]); $page = $this->actingAs($user)->visit("/budgets/{$budget->id}"); $page->wait(2); // Wait for page to fully load $page->assertSee($budget->name) ->wait(2) ->waitForText('Budgets', 10) ->wait(1) // Extra wait before clicking ->click('nav[aria-label="breadcrumb"] a:has-text("Budgets")') ->wait(4) // Wait for navigation ->assertPathIs('/budgets') ->assertNoJavascriptErrors(); });