diff --git a/test/helpers/budgets_helper_test.rb b/test/helpers/budgets_helper_test.rb
index 4c94f627e..e42877657 100644
--- a/test/helpers/budgets_helper_test.rb
+++ b/test/helpers/budgets_helper_test.rb
@@ -104,4 +104,49 @@ class BudgetsHelperTest < ActionView::TestCase
assert_equal [ child.id ], group.budget_subcategories.map(&:category_id)
assert group.budget_subcategories.first.any_over_budget?
end
+
+ test "budget has over budget when uncategorized spending exceeds its allocation" do
+ family = Family.create!(name: "Helper Budget Repro", currency: "USD")
+ account = Account.create!(
+ family: family,
+ accountable: Depository.new,
+ name: "Checking",
+ status: "active",
+ currency: "USD",
+ balance: 0
+ )
+
+ category = Category.create!(
+ name: "Helper Groceries #{SecureRandom.hex(4)}",
+ family: family,
+ color: "#407706",
+ lucide_icon: "shopping-bag"
+ )
+
+ budget = Budget.create!(
+ family: family,
+ start_date: Date.current.beginning_of_month,
+ end_date: Date.current.end_of_month,
+ currency: "USD",
+ budgeted_spending: 100
+ )
+
+ BudgetCategory.create!(
+ budget: budget,
+ category: category,
+ budgeted_spending: 100,
+ currency: "USD"
+ )
+
+ Entry.create!(
+ account: account,
+ entryable: Transaction.create!(category: nil),
+ date: Date.current,
+ name: "Helper Uncategorized Over Budget",
+ amount: 125,
+ currency: "USD"
+ )
+
+ assert budget_has_over_budget?(Budget.find(budget.id))
+ end
end
diff --git a/test/models/budget_test.rb b/test/models/budget_test.rb
index 56e791e75..26e1b7653 100644
--- a/test/models/budget_test.rb
+++ b/test/models/budget_test.rb
@@ -263,6 +263,7 @@ class BudgetTest < ActiveSupport::TestCase
budget.stubs(:available_to_spend).returns(200)
budget.stubs(:budget_category_actual_spending).with(parent_budget_category).returns(63.11)
budget.stubs(:budget_category_actual_spending).with(standalone_budget_category).returns(25)
+ budget.stubs(:budget_category_actual_spending).with(budget.uncategorized_budget_category).returns(0)
segments = budget.to_donut_segments_json
@@ -280,6 +281,59 @@ class BudgetTest < ActiveSupport::TestCase
assert_equal 200, segments_by_id["unused"][:amount]
end
+ test "to_donut_segments_json includes uncategorized spending" do
+ family = @family
+ account = Account.create!(
+ family: family,
+ accountable: Depository.new,
+ name: "Checking",
+ status: "active",
+ currency: "USD",
+ balance: 0
+ )
+
+ category = Category.create!(
+ name: "Groceries #{Time.now.to_f}",
+ family: family,
+ color: "#407706",
+ lucide_icon: "shopping-bag"
+ )
+
+ budget = Budget.create!(
+ family: family,
+ start_date: Date.current.beginning_of_month,
+ end_date: Date.current.end_of_month,
+ currency: "USD",
+ budgeted_spending: 100
+ )
+
+ BudgetCategory.create!(
+ budget: budget,
+ category: category,
+ budgeted_spending: 100,
+ currency: "USD"
+ )
+
+ Entry.create!(
+ account: account,
+ entryable: Transaction.create!(category: nil),
+ date: Date.current,
+ name: "Uncategorized donut spending",
+ amount: 125,
+ currency: "USD"
+ )
+
+ budget = Budget.find(budget.id)
+ uncategorized = budget.uncategorized_budget_category
+ segments = budget.to_donut_segments_json
+ uncategorized_segment = segments.find { |segment| segment[:id] == uncategorized.id }
+
+ assert_equal 125, budget.actual_spending
+ assert_equal 125, uncategorized.actual_spending
+ assert_not_nil uncategorized_segment
+ assert_equal 125, uncategorized_segment[:amount]
+ end
+
test "actual_spending subtracts uncategorized refunds" do
family = families(:dylan_family)
budget = Budget.find_or_bootstrap(family, start_date: Date.current.beginning_of_month)
diff --git a/test/views/budgets/budget_donut_view_test.rb b/test/views/budgets/budget_donut_view_test.rb
new file mode 100644
index 000000000..0fe5550c5
--- /dev/null
+++ b/test/views/budgets/budget_donut_view_test.rb
@@ -0,0 +1,54 @@
+require "test_helper"
+
+class BudgetDonutViewTest < ActionView::TestCase
+ test "renders hover content for uncategorized donut segment" do
+ family = Family.create!(name: "Budget Donut View Repro", currency: "USD")
+ account = Account.create!(
+ family: family,
+ accountable: Depository.new,
+ name: "Checking",
+ status: "active",
+ currency: "USD",
+ balance: 0
+ )
+
+ category = Category.create!(
+ name: "Groceries #{SecureRandom.hex(4)}",
+ family: family,
+ color: "#407706",
+ lucide_icon: "shopping-bag"
+ )
+
+ budget = Budget.create!(
+ family: family,
+ start_date: Date.current.beginning_of_month,
+ end_date: Date.current.end_of_month,
+ currency: "USD",
+ budgeted_spending: 100
+ )
+
+ BudgetCategory.create!(
+ budget: budget,
+ category: category,
+ budgeted_spending: 100,
+ currency: "USD"
+ )
+
+ Entry.create!(
+ account: account,
+ entryable: Transaction.create!(category: nil),
+ date: Date.current,
+ name: "Uncategorized hover spending",
+ amount: 125,
+ currency: "USD"
+ )
+
+ budget = Budget.find(budget.id)
+ uncategorized = budget.uncategorized_budget_category
+
+ html = render(partial: "budgets/budget_donut", locals: { budget: budget })
+
+ assert_includes html, "segment_#{uncategorized.id}"
+ assert_includes html, uncategorized.category.display_name
+ end
+end