Files
sure/test/controllers/accounts_controller_test.rb
Copilot 3199c9b76d Prevent long category labels from overflowing or crowding adjacent controls (#1498)
* Initial plan

* Fix category delete dialog dropdown overflow

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/200da7a4-fd59-4ae4-a709-f631ccf21e8c

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Tighten category deletion regression test

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/200da7a4-fd59-4ae4-a709-f631ccf21e8c

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Fix deletion button text overflow

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/e802e01f-079e-4322-ba03-b222ab5d4b84

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Preserve category menu spacing on mobile

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/74b5dd1e-7935-4356-806a-759bff911930

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Prevent account activity label overlap on mobile

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/e94027d6-e230-44c8-99a1-6e5645bec82b

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Fix wide account activity category overflow

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/4ad79894-2935-47a3-8d37-037e2bd14376

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Linter

* Fix flaky system tests in CI

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/3507447f-363f-4759-807c-c62a2858d270

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

* Reset system test viewport between tests

Agent-Logs-Url: https://github.com/we-promise/sure/sessions/357a43b1-11c5-49be-972d-0592a37d97b1

Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-04-19 18:40:50 +02:00

350 lines
12 KiB
Ruby

require "test_helper"
class AccountsControllerTest < ActionDispatch::IntegrationTest
include ActionView::RecordIdentifier
setup do
sign_in @user = users(:family_admin)
@account = accounts(:depository)
end
test "should get index" do
get accounts_url
assert_response :success
assert_select "p.ml-auto.privacy-sensitive"
end
test "should get show" do
get account_url(@account)
assert_response :success
end
test "account activity marks trade amounts as privacy-sensitive" do
trade_entry = entries(:trade)
expected_amount = ApplicationController.helpers.format_money(-trade_entry.amount_money)
get account_url(accounts(:investment))
assert_response :success
assert_select "turbo-frame##{dom_id(trade_entry)} p.privacy-sensitive", text: expected_amount, count: 1
end
test "activity pagination keeps activity tab when loaded from holdings tab" do
investment = accounts(:investment)
11.times do |i|
Entry.create!(
account: investment,
name: "Test investment activity #{i}",
date: Date.current - i.days,
amount: 10 + i,
currency: investment.currency,
entryable: Transaction.new
)
end
get account_url(investment, tab: "holdings")
assert_response :success
assert_select "a[href*='page=2'][href*='tab=activity']"
assert_select "a[href*='page=2'][href*='tab=holdings']", count: 0
end
test "account activity constrains long category labels before the amount on wide screens" do
category = categories(:food_and_drink)
category.update!(name: "Super Long Category Name That Should Stop Before The Amount On Wide Screens Too")
entry = @account.entries.create!(
name: "Wide category verification",
date: Date.current,
amount: 187.65,
currency: @account.currency,
entryable: Transaction.new(category: category)
)
get account_url(@account, tab: "activity")
assert_response :success
assert_select "##{dom_id(entry.entryable, "category_menu_desktop")}"
assert_select "##{dom_id(entry.entryable, "category_menu_desktop")}.min-w-0"
assert_select "##{dom_id(entry.entryable, "category_menu_desktop")}.overflow-hidden"
assert_select "##{dom_id(entry.entryable, "category_menu_desktop")} button.block"
assert_select "##{dom_id(entry.entryable, "category_menu_desktop")} button.w-full"
assert_select "##{dom_id(entry.entryable, "category_menu_desktop")} button.overflow-hidden"
assert_select "##{dom_id(entry.entryable, "category_menu_desktop")} [data-testid='category-name']"
assert_select "div.hidden.md\\:flex.min-w-0"
end
test "should sync account" do
post sync_account_url(@account)
assert_redirected_to account_url(@account)
end
test "should get sparkline" do
get sparkline_account_url(@account)
assert_response :success
end
test "destroys account" do
delete account_url(@account)
assert_redirected_to accounts_path
assert_enqueued_with job: DestroyJob
assert_equal "Depository account scheduled for deletion", flash[:notice]
end
test "syncing linked account triggers sync for all provider items" do
plaid_account = plaid_accounts(:one)
plaid_item = plaid_account.plaid_item
AccountProvider.create!(account: @account, provider: plaid_account)
# Reload to ensure the account has the provider association loaded
@account.reload
# Mock at the class level since controller loads account from DB
Account.any_instance.expects(:syncing?).returns(false)
PlaidItem.any_instance.expects(:syncing?).returns(false)
PlaidItem.any_instance.expects(:sync_later).once
post sync_account_url(@account)
assert_redirected_to account_url(@account)
end
test "syncing unlinked account calls account sync_later" do
Account.any_instance.expects(:syncing?).returns(false)
Account.any_instance.expects(:sync_later).once
post sync_account_url(@account)
assert_redirected_to account_url(@account)
end
test "confirms unlink for linked account" do
plaid_account = plaid_accounts(:one)
AccountProvider.create!(account: @account, provider: plaid_account)
get confirm_unlink_account_url(@account)
assert_response :success
end
test "redirects when confirming unlink for unlinked account" do
get confirm_unlink_account_url(@account)
assert_redirected_to account_url(@account)
assert_equal "Account is not linked to a provider", flash[:alert]
end
test "unlinks linked account successfully with new system" do
plaid_account = plaid_accounts(:one)
AccountProvider.create!(account: @account, provider: plaid_account)
@account.reload
assert @account.linked?
delete unlink_account_url(@account)
@account.reload
assert_not @account.linked?
assert_redirected_to accounts_path
assert_equal "Account unlinked successfully. It is now a manual account.", flash[:notice]
end
test "unlinks linked account successfully with legacy system" do
plaid_account = plaid_accounts(:one)
@account.update!(plaid_account_id: plaid_account.id)
@account.reload
assert @account.linked?
delete unlink_account_url(@account)
@account.reload
assert_not @account.linked?
assert_nil @account.plaid_account_id
assert_redirected_to accounts_path
assert_equal "Account unlinked successfully. It is now a manual account.", flash[:notice]
end
test "redirects when unlinking unlinked account" do
delete unlink_account_url(@account)
assert_redirected_to account_url(@account)
assert_equal "Account is not linked to a provider", flash[:alert]
end
test "unlinked account can be deleted" do
plaid_account = plaid_accounts(:one)
AccountProvider.create!(account: @account, provider: plaid_account)
@account.reload
# Cannot delete while linked
delete account_url(@account)
assert_redirected_to account_url(@account)
assert_equal "Cannot delete a linked account. Please unlink it first.", flash[:alert]
# Unlink the account
delete unlink_account_url(@account)
@account.reload
# Now can delete
delete account_url(@account)
assert_redirected_to accounts_path
assert_enqueued_with job: DestroyJob
assert_equal "Depository account scheduled for deletion", flash[:notice]
end
test "disabling an account keeps it visible on index" do
@account.disable!
get accounts_path
assert_response :success
assert_includes @response.body, @account.name
end
test "toggle_active disables and re-enables an account" do
patch toggle_active_account_url(@account)
assert_redirected_to accounts_path
@account.reload
assert @account.disabled?
patch toggle_active_account_url(@account)
assert_redirected_to accounts_path
@account.reload
assert @account.active?
end
test "select_provider shows available providers" do
get select_provider_account_url(@account)
assert_response :success
end
test "set_default sets user default account" do
patch set_default_account_url(@account)
assert_redirected_to accounts_path
@user.reload
assert_equal @account.id, @user.default_account_id
end
test "set_default rejects ineligible account type" do
investment = accounts(:investment)
patch set_default_account_url(investment)
assert_redirected_to accounts_path
assert_equal I18n.t("accounts.set_default.depository_only"), flash[:alert]
@user.reload
assert_not_equal investment.id, @user.default_account_id
end
test "remove_default clears user default account" do
@user.update!(default_account: @account)
patch remove_default_account_url(@account)
assert_redirected_to accounts_path
@user.reload
assert_nil @user.default_account_id
end
test "select_provider redirects for already linked account" do
plaid_account = plaid_accounts(:one)
AccountProvider.create!(account: @account, provider: plaid_account)
get select_provider_account_url(@account)
assert_redirected_to account_url(@account)
assert_equal "Account is already linked to a provider", flash[:alert]
end
test "unlink preserves SnaptradeAccount record" do
snaptrade_account = snaptrade_accounts(:fidelity_401k)
investment = accounts(:investment)
AccountProvider.create!(account: investment, provider: snaptrade_account)
investment.reload
assert investment.linked?
delete unlink_account_url(investment)
investment.reload
assert_not investment.linked?
assert_redirected_to accounts_path
# SnaptradeAccount should still exist (not destroyed)
assert SnaptradeAccount.exists?(snaptrade_account.id), "SnaptradeAccount should be preserved after unlink"
# But AccountProvider should be gone
assert_not AccountProvider.exists?(provider_type: "SnaptradeAccount", provider_id: snaptrade_account.id)
end
test "unlink does not enqueue SnapTrade cleanup job" do
snaptrade_account = snaptrade_accounts(:fidelity_401k)
investment = accounts(:investment)
AccountProvider.create!(account: investment, provider: snaptrade_account)
investment.reload
assert_no_enqueued_jobs(only: SnaptradeConnectionCleanupJob) do
delete unlink_account_url(investment)
end
end
test "unlink detaches holdings from SnapTrade provider" do
snaptrade_account = snaptrade_accounts(:fidelity_401k)
investment = accounts(:investment)
ap = AccountProvider.create!(account: investment, provider: snaptrade_account)
# Assign a holding to this provider
holding = holdings(:one)
holding.update!(account_provider: ap)
delete unlink_account_url(investment)
holding.reload
assert_nil holding.account_provider_id, "Holding should be detached from provider after unlink"
end
end
class AccountsControllerSimplefinCtaTest < ActionDispatch::IntegrationTest
fixtures :users, :families
setup do
sign_in users(:family_admin)
@family = families(:dylan_family)
end
test "when unlinked SFAs exist and manuals exist, shows setup button only" do
item = SimplefinItem.create!(family: @family, name: "Conn", access_url: "https://example.com/access")
# Unlinked SFA (no account and no provider link)
item.simplefin_accounts.create!(name: "A", account_id: "sf_a", currency: "USD", current_balance: 1, account_type: "depository")
# One manual account available
Account.create!(family: @family, name: "Manual A", currency: "USD", balance: 0, accountable_type: "Depository", accountable: Depository.create!(subtype: "checking"))
get accounts_path
assert_response :success
# Expect setup link present
assert_includes @response.body, setup_accounts_simplefin_item_path(item)
# Relink modal (SimpleFin-specific) should not be present anymore
refute_includes @response.body, "Link existing accounts"
end
test "when SFAs exist and none unlinked and manuals exist, no relink modal is shown (unified flow)" do
item = SimplefinItem.create!(family: @family, name: "Conn2", access_url: "https://example.com/access")
# Create a manual linked to SFA so unlinked count == 0
sfa = item.simplefin_accounts.create!(name: "B", account_id: "sf_b", currency: "USD", current_balance: 1, account_type: "depository")
linked = Account.create!(family: @family, name: "Linked", currency: "USD", balance: 0, accountable_type: "Depository", accountable: Depository.create!(subtype: "savings"))
# Legacy association sufficient to count as linked
sfa.update!(account: linked)
# Also create another manual account to make manuals_exist true
Account.create!(family: @family, name: "Manual B", currency: "USD", balance: 0, accountable_type: "Depository", accountable: Depository.create!(subtype: "checking"))
get accounts_path
assert_response :success
# The SimpleFin-specific relink modal is removed in favor of unified provider flow
refute_includes @response.body, "Link existing accounts"
end
test "when no SFAs exist, shows neither CTA" do
item = SimplefinItem.create!(family: @family, name: "Conn3", access_url: "https://example.com/access")
get accounts_path
assert_response :success
refute_includes @response.body, setup_accounts_simplefin_item_path(item)
refute_includes @response.body, "Link existing accounts"
end
end