Files
sure/test/system/accounts_test.rb
ghost 875b58d9ca design-system(mobile): SureFontWeights tokens (named weight tiers) (#2419)
* feat(mobile): add SureFontWeights design tokens

Introduce named font-weight tokens so widgets reference a DS tier instead
of a raw FontWeight, mirroring the web design system's Tailwind weight
utilities (font-medium / font-semibold).

- design/tokens/sure.tokens.json: add font.weight.medium (500) and
  .semibold (600) as the canonical source.
- generate_sure_tokens.mjs: emit SureTokens.weightMedium / weightSemibold
  (with a font-weight resolver + validation), regenerate sure_tokens.dart.
- sure_tokens_test.dart: assert the generated weights match canonical.
- Migrate the dashboard-area money/hierarchy sites onto the tokens
  (net_worth_card, account_card, dashboard, recent_transactions,
  transactions_list): FontWeight.w500 -> SureTokens.weightMedium,
  FontWeight.w600 -> SureTokens.weightSemibold.

Weight-only; other screens adopt the tokens in their own polish PRs.
166 tests pass; flutter analyze: no new issues; token generator --check
is clean.

* build(tokens): regenerate web _generated.css for new font weights

Adding font.weight.medium/semibold to sure.tokens.json also flows through
the web token generator (bin/tokens.mjs), which emits --font-weight-medium
and --font-weight-semibold. Commit the regenerated _generated.css so the
tokens:check gate stays green (node bin/tokens.mjs + git diff --quiet).

* test(system): de-flake account edit via account-menu

AccountsTest#assert_account_created opened the account menu and clicked
Edit immediately after visiting the account page. That page issues a
Turbo morph refresh shortly after load (turbo_refreshes_with :morph
reacting to a family-stream broadcast), which can detach the menu node
mid-click — "Selenium::WebDriver::Error: Node with given id does not
belong to the document" — or wipe the just-opened edit form. Capybara
does not auto-retry that inspector error, so the test flaked
intermittently in CI.

Extract open_account_edit_dialog, which retries the menu interaction
until the edit form is present, rescuing the transient detach/stale
errors. Mirrors the existing PropertyTest#open_account_edit_dialog
precedent for the same morph race, but also tolerates the click itself
raising while the refresh is in flight.
2026-06-21 11:54:29 +02:00

201 lines
6.8 KiB
Ruby

require "application_system_test_case"
class AccountsTest < ApplicationSystemTestCase
setup do
sign_in @user = users(:family_admin)
Family.any_instance.stubs(:get_link_token).returns("test-link-token")
visit root_url
open_new_account_modal
end
test "can create depository account" do
assert_account_created("Depository")
end
test "can create investment account" do
assert_account_created("Investment")
end
test "can create crypto account" do
assert_account_created("Crypto")
end
test "can create property account" do
# Step 1: Select property type and enter basic details
click_link "Property"
account_name = "[system test] Property Account"
fill_in "Name*", with: account_name
select "Single Family Home", from: "Property type*"
fill_in "Year Built (optional)", with: 2005
fill_in "Area (optional)", with: 2250
click_button "Next"
# Step 2: Enter balance information
assert_text "Value"
fill_in "account[balance]", with: 500000
click_button "Next"
# Step 3: Enter address information
assert_text "Address"
fill_in "Address Line 1", with: "123 Main St"
fill_in "City", with: "San Francisco"
fill_in "State/Region", with: "CA"
fill_in "Postal Code", with: "94101"
fill_in "Country", with: "US"
click_button "Save"
# Verify account was created and is now active
assert_text account_name
created_account = Account.order(:created_at).last
assert_equal "active", created_account.status
assert_equal 500000, created_account.balance
assert_equal "123 Main St", created_account.property.address.line1
assert_equal "San Francisco", created_account.property.address.locality
end
test "can create vehicle account" do
assert_account_created "Vehicle" do
fill_in "Make", with: "Toyota"
fill_in "Model", with: "Camry"
fill_in "Year", with: "2020"
fill_in "Mileage", with: "30000"
end
end
test "can create other asset account" do
assert_account_created("OtherAsset")
end
test "can create credit card account" do
assert_account_created "CreditCard" do
fill_in "Available credit", with: 1000
fill_in "account[accountable_attributes][minimum_payment]", with: 25.51
fill_in "APR", with: 15.25
fill_in "Expiration date", with: 1.year.from_now.to_date
fill_in "Annual fee", with: 100
end
end
test "can create loan account" do
assert_account_created "Loan" do
fill_in "account[accountable_attributes][initial_balance]", with: 1000
fill_in "Interest rate", with: 5.25
select "Fixed", from: "Rate type"
fill_in "Term (months)", with: 360
end
end
test "can create other liability account" do
assert_account_created("OtherLiability")
end
private
def open_new_account_modal
within "[data-controller='DS--tabs']" do
click_button "All"
click_link "New account"
end
end
# The account page issues a Turbo morph refresh shortly after it loads
# (`turbo_refreshes_with method: :morph` reacting to a family-stream
# broadcast). Opening the menu while that refresh is in flight can detach the
# node mid-click ("Node with given id does not belong to the document") or
# wipe the just-opened edit form — neither of which Capybara auto-retries.
# Retry until the edit form is present so the test is deterministic instead
# of racing the broadcast (mirrors PropertyTest#open_account_edit_dialog).
def open_account_edit_dialog
3.times do
# A prior (slow) attempt may have already opened the edit form.
return if has_field?("Account name", wait: 0)
begin
within_testid("account-menu") do
# Open the menu only when it's closed. DS::Menu's trigger toggles
# (menu_controller#toggle), so blindly re-clicking an already-open
# menu would close it and hide "Edit", turning a slow-but-successful
# modal load into a fresh flake.
unless has_selector?("[role='menu']", visible: true, wait: 0)
find("button").click
end
click_on "Edit"
end
rescue Selenium::WebDriver::Error::WebDriverError => e
raise unless e.message.match?(
/does not belong to the document|stale element reference/i,
)
next
end
return if has_field?("Account name", wait: 2)
end
assert_field "Account name"
end
def assert_account_created(accountable_type, &block)
click_link Accountable.from_type(accountable_type).singular_display_name
click_link "Enter account balance" if accountable_type.in?(%w[Depository Investment Crypto Loan CreditCard])
account_name = "[system test] #{accountable_type} Account"
institution_name = "[system test] Institution"
institution_domain = "example.com"
notes = "Test notes for #{accountable_type}"
fill_in "Account name*", with: account_name
fill_in "account[balance]", with: 100.99
find("summary", text: "Additional details").click
fill_in "Institution name", with: institution_name
fill_in "Institution domain", with: institution_domain
fill_in "Notes", with: notes
yield if block_given?
click_button "Create Account"
within_testid("account-sidebar-tabs") do
click_on "All"
find("details", text: Accountable.from_type(accountable_type).display_name).click
assert_text account_name
end
visit accounts_url
assert_text account_name
created_account = Account.order(:created_at).last
assert_equal institution_name, created_account[:institution_name]
assert_equal institution_domain, created_account[:institution_domain]
assert_equal notes, created_account[:notes]
visit account_url(created_account)
open_account_edit_dialog
updated_institution_name = "[system test] Updated Institution"
updated_institution_domain = "updated.example.com"
updated_notes = "Updated notes for #{accountable_type}"
fill_in "Account name", with: "Updated account name"
find("summary", text: "Additional details").click
fill_in "Institution name", with: updated_institution_name
fill_in "Institution domain", with: updated_institution_domain
fill_in "Notes", with: updated_notes
click_button "Update Account"
assert_selector "h2", text: "Updated account name"
created_account.reload
assert_equal updated_institution_name, created_account[:institution_name]
assert_equal updated_institution_domain, created_account[:institution_domain]
assert_equal updated_notes, created_account[:notes]
end
def humanized_accountable(accountable_type)
Accountable.from_type(accountable_type).singular_display_name
end
end