mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 07:49:01 +00:00
Merge origin/feat/goals-v2-architecture; reconcile beta→preview rename
Remote branch added a beta_gated_nav_item helper + 'Gating the main nav' docs section. Main concurrently renamed the beta-features gate to preview-features (concern, predicate, JSONB key, locale flash). Rename the new helper / partial local / pill marker to match preview naming and port the nav-gating docs into gating-a-preview-feature.md so the improvement survives the rename. Resolved conflicts: - db/schema.rb: take the later schema version (2026_05_19_100000). - docs/llm-guides/gating-a-beta-feature.md: accept main's deletion; port the 'Gating the main nav' section into the preview guide. Renames carried through to keep the gate wired end-to-end: - application_helper.rb: beta_gated_nav_item → preview_gated_nav_item; beta_features_enabled? → preview_features_enabled?; beta: → preview:. - _nav_item.html.erb: beta: local → preview: local; shared.beta i18n key → shared.preview. - application.html.erb: caller renamed to preview_gated_nav_item. - goals/index.html.erb: pill label uses shared.preview. - shared/en.yml: 'beta: Beta' → 'preview: Preview'. - goals_controller, goal_pledges_controller: require_beta_features! → require_preview_features!. - goals_controller_test, goal_pledges_controller_test: flip the preference key, flash matcher, and test names to 'preview'.
This commit is contained in:
@@ -2,13 +2,24 @@ require "test_helper"
|
||||
|
||||
class GoalsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in users(:family_admin)
|
||||
@user = users(:family_admin)
|
||||
@user.update!(preferences: (@user.preferences || {}).merge("preview_features_enabled" => true))
|
||||
sign_in @user
|
||||
@goal = goals(:vacation_italy)
|
||||
@depository = accounts(:depository)
|
||||
@connected = accounts(:connected)
|
||||
ensure_tailwind_build
|
||||
end
|
||||
|
||||
test "redirects users without preview access" do
|
||||
@user.update!(preferences: (@user.preferences || {}).merge("preview_features_enabled" => false))
|
||||
|
||||
get goals_url
|
||||
|
||||
assert_redirected_to root_path
|
||||
assert_match(/preview/i, flash[:alert])
|
||||
end
|
||||
|
||||
test "index renders with active filter by default" do
|
||||
get goals_url
|
||||
assert_response :success
|
||||
@@ -151,6 +162,72 @@ class GoalsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_redirected_to goals_path
|
||||
end
|
||||
|
||||
test "index KPI swaps to 'All caught up' when every tracked goal is reached" do
|
||||
family = users(:family_admin).family
|
||||
family.goals.destroy_all
|
||||
# Real reached state: target $1 against the depository fixture's
|
||||
# $5000 balance. Stubbing :status hides whether the controller
|
||||
# actually reads the right method on each goal.
|
||||
build_goal(family, "Wedding", target_amount: 1, target_date: 1.year.from_now)
|
||||
|
||||
get goals_url
|
||||
assert_response :success
|
||||
assert_match(/All caught up/i, response.body)
|
||||
assert_match(/1\s*reached/i, response.body)
|
||||
end
|
||||
|
||||
test "index KPI 'on track' denominator excludes no-target-date goals" do
|
||||
family = users(:family_admin).family
|
||||
family.goals.destroy_all
|
||||
# One trackable goal (has target_date) + one open-ended (no target_date).
|
||||
# The trackable one should be the only thing in the denominator;
|
||||
# open-ended goals can't be off pace because they have no required pace.
|
||||
build_goal(family, "House", target_amount: 1_000_000, target_date: 1.year.from_now)
|
||||
build_goal(family, "Emergency", target_amount: 1_000_000, target_date: nil)
|
||||
|
||||
get goals_url
|
||||
assert_response :success
|
||||
# Expect "0 of 1" — the open-ended goal stays out of the fraction
|
||||
# even though it's active.
|
||||
assert_match(/0\s*of\s*1/i, response.body)
|
||||
assert_match(/without a deadline/i, response.body)
|
||||
end
|
||||
|
||||
private
|
||||
def build_goal(family, name, target_amount: 1_000_000, target_date: nil)
|
||||
g = family.goals.new(name: name, target_amount: target_amount, target_date: target_date, currency: "USD")
|
||||
g.goal_accounts.build(account: @depository)
|
||||
g.save!
|
||||
g
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
test "create ignores forbidden params (family_id, state)" do
|
||||
family = users(:family_admin).family
|
||||
other_family = Family.create!(name: "Other", currency: "USD", locale: "en", country: "US", timezone: "UTC")
|
||||
|
||||
assert_difference -> { family.goals.count }, 1 do
|
||||
post goals_url, params: {
|
||||
goal: {
|
||||
name: "Hijack target",
|
||||
target_amount: 100,
|
||||
currency: "USD",
|
||||
state: "archived",
|
||||
family_id: other_family.id,
|
||||
account_ids: [ @depository.id ]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
goal = family.goals.order(:created_at).last
|
||||
# Strong params must strip both `state` (AASM-managed) and `family_id`
|
||||
# (cross-family pivot) — otherwise a crafted POST would create rows
|
||||
# outside the current family or skip the active-state assumption.
|
||||
assert_equal "active", goal.state
|
||||
assert_equal family.id, goal.family_id
|
||||
end
|
||||
|
||||
test "another family's goal returns 404" do
|
||||
other_family = Family.create!(name: "Other", currency: "USD", locale: "en", country: "US", timezone: "UTC")
|
||||
other_account = Account.create!(family: other_family, accountable: Depository.new, name: "Foreign", currency: "USD", balance: 100)
|
||||
|
||||
Reference in New Issue
Block a user