onboarded()->create(); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->assertSee('Manage your transaction categories') ->assertNoJavascriptErrors(); }); it('shows existing categories in list', function () { $user = User::factory()->onboarded()->create(); $category = Category::factory()->create([ 'user_id' => $user->id, 'name' => 'Groceries', 'icon' => 'ShoppingCart', 'color' => 'green', ]); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->waitForText('Groceries') ->assertNoJavascriptErrors(); }); it('vertically centers category table cells', function () { $user = User::factory()->onboarded()->create(); Category::factory()->create([ 'user_id' => $user->id, 'name' => 'Investments', 'icon' => 'ChartNoAxesColumnIncreasing', 'color' => 'lime', 'type' => 'transfer', ]); actingAs($user); $page = visit('/settings/categories'); $page->waitForText('Investments') ->assertAttributeContains( 'tbody tr td[data-slot="table-cell"]:first-child', 'class', 'align-middle', ) ->assertNoJavascriptErrors(); }); it('can open create category dialog', function () { $user = User::factory()->onboarded()->create(); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->click('Create Category') ->wait(0.5) ->assertSee('Add a new category to organize your transactions') ->assertNoJavascriptErrors(); }); it('can create a new category', function () { $user = User::factory()->onboarded()->create(); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->click('Create Category') ->wait(0.5) ->fill('name', 'Entertainment') ->click('Select an icon') ->wait(0.5) ->click('//div[@role="option"][1]') ->wait(0.3) ->click('Select a color') ->wait(0.5) ->click('//div[@role="option"][contains(., "purple")]') ->wait(0.3) ->click('Select a type') ->wait(0.5) ->click('//div[@role="option"][contains(., "Expense")]') ->wait(0.3) ->assertSee('Cashflow and charts') ->assertSee('Expense categories count as cash outflow and appear in spending charts, including top spending categories') ->click('button[type="submit"]') ->wait(2) ->assertSee('Entertainment') ->assertNoJavascriptErrors(); $this->assertDatabaseHas('categories', [ 'user_id' => $user->id, 'name' => 'Entertainment', 'color' => 'purple', 'type' => 'expense', 'cashflow_direction' => 'hidden', ]); }); it('explains savings and investment category cashflow impact in the create dialog', function (string $type, string $expectedText) { $user = User::factory()->onboarded()->create(); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->click('Create Category') ->wait(0.5) ->click('Select a type') ->wait(0.5) ->click("//div[@role=\"option\"][contains(., \"{$type}\")]") ->wait(0.3) ->assertSee('Cashflow and charts') ->assertSee($expectedText) ->assertSee('stay out of income, expenses, and top spending categories') ->assertNoJavascriptErrors(); })->with([ ['Savings', 'Savings categories appear as saved money at the top of cashflow and as cash outflow in the cashflow chart'], ['Investment', 'Investment categories appear as invested money at the top of cashflow and as cash outflow in the cashflow chart'], ]); it('shows locked income cashflow direction in the create dialog', function () { $user = User::factory()->onboarded()->create(); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->click('Create Category') ->wait(0.5) ->assertSee('Cashflow and charts') ->assertSee('Choose a category type to see how it affects cashflow and charts') ->click('Select a type') ->wait(0.5) ->click('//div[@role="option"][contains(., "Income")]') ->wait(0.3) ->assertSee('Income categories count as cash inflow and appear in income charts') ->assertSee('They do not appear in top spending categories') ->assertNoJavascriptErrors(); }); it('can create a new transfer category with a cashflow analytics direction', function () { $user = User::factory()->onboarded()->create(); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->click('Create Category') ->wait(0.5) ->fill('name', 'Emergency Fund') ->click('Select an icon') ->wait(0.5) ->click('//div[@role="option"][contains(., "PiggyBank")]') ->wait(0.3) ->click('Select a color') ->wait(0.5) ->click('//div[@role="option"][contains(., "lime")]') ->wait(0.3) ->click('Select a type') ->wait(0.5) ->click('//div[@role="option"][contains(., "Transfer")]') ->wait(0.3) ->assertSee('Cashflow and charts') ->assertSee('Choose whether to show them in the cashflow chart') ->click('Do not show in cashflow chart') ->wait(0.5) ->click('//div[@role="option"][contains(., "Show in cashflow chart as outflow")]') ->wait(0.3) ->click('Save') ->wait(2) ->assertSee('Emergency Fund') ->assertNoJavascriptErrors(); $this->assertDatabaseHas('categories', [ 'user_id' => $user->id, 'name' => 'Emergency Fund', 'color' => 'lime', 'type' => 'transfer', 'cashflow_direction' => 'outflow', ]); }); it('can filter categories by name', function () { $user = User::factory()->onboarded()->create(); Category::factory()->create([ 'user_id' => $user->id, 'name' => 'Groceries', ]); Category::factory()->create([ 'user_id' => $user->id, 'name' => 'Entertainment', ]); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->waitForText('Groceries') ->fill('input[placeholder="Filter categories..."]', 'Groceries') ->wait(0.5) ->assertSee('Groceries') ->assertNoJavascriptErrors(); }); it('shows empty state when no categories exist', function () { $user = User::factory()->onboarded()->create(); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->waitForText('No categories found') ->assertNoJavascriptErrors(); }); it('can edit an existing category via context menu', function () { $user = User::factory()->onboarded()->create(); $category = Category::factory()->create([ 'user_id' => $user->id, 'name' => 'Old Category', 'icon' => 'Tag', 'color' => 'blue', ]); actingAs($user); $page = visit('/settings/categories'); $page->waitForText('Old Category') ->rightClick('Old Category') ->wait(0.5) ->click('Edit') ->wait(0.5) ->assertSee('Edit Category') ->fill('name', 'Updated Category') ->click('//button[contains(., "Update")]') ->wait(2) ->assertSee('Updated Category') ->assertNoJavascriptErrors(); $this->assertDatabaseHas('categories', [ 'id' => $category->id, 'name' => 'Updated Category', ]); }); it('shows transfer type description when transfer type is selected in create dialog', function () { $user = User::factory()->onboarded()->create(); actingAs($user); $page = visit('/settings/categories'); $page->assertSee('Categories settings') ->click('Create Category') ->wait(1) ->assertSee('Add a new category to organize your transactions') ->assertSee('Select a type') ->click('//button[contains(., "Select a type")]') ->wait(0.5) ->click('//div[@role="option"][contains(., "Transfer")]') ->wait(0.5) ->assertSee('Transfer categories are excluded from income, expenses, and top spending categories') ->assertSee('Choose whether to show them in the cashflow chart') ->assertNoJavascriptErrors(); }); it('shows transfer type description when transfer type is selected in edit dialog', function () { $user = User::factory()->onboarded()->create(); $category = Category::factory()->create([ 'user_id' => $user->id, 'name' => 'Test Category', 'icon' => 'Tag', 'color' => 'blue', 'type' => 'expense', ]); actingAs($user); $page = visit('/settings/categories'); $page->waitForText('Test Category') ->rightClick('Test Category') ->wait(0.5) ->click('Edit') ->wait(1) ->assertSee('Edit Category') ->click('//button[contains(., "Expense")]') ->wait(0.5) ->click('//div[@role="option"][contains(., "Transfer")]') ->wait(0.5) ->assertSee('Transfer categories are excluded from income, expenses, and top spending categories') ->assertSee('Choose whether to show them in the cashflow chart') ->assertNoJavascriptErrors(); });