diff --git a/app/models/category.rb b/app/models/category.rb index 9a83488fc..c4239879d 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -109,6 +109,14 @@ class Category < ApplicationRecord I18n.t(UNCATEGORIZED_NAME_KEY) end + # Returns all possible uncategorized names across all supported locales + # Used to detect uncategorized filter regardless of URL parameter language + def all_uncategorized_names + LanguagesHelper::SUPPORTED_LOCALES.map do |locale| + I18n.t(UNCATEGORIZED_NAME_KEY, locale: locale) + end.uniq + end + # Helper to get the localized name for other investments def other_investments_name I18n.t(OTHER_INVESTMENTS_NAME_KEY) diff --git a/app/models/transaction/search.rb b/app/models/transaction/search.rb index 287dae467..1bee4ecb6 100644 --- a/app/models/transaction/search.rb +++ b/app/models/transaction/search.rb @@ -102,10 +102,10 @@ class Transaction::Search def apply_category_filter(query, categories) return query unless categories.present? - # Remove "Uncategorized" from category names to query the database - uncategorized_name = Category.uncategorized.name - include_uncategorized = categories.include?(uncategorized_name) - real_categories = categories - [ uncategorized_name ] + # Check for "Uncategorized" in any supported locale (handles URL params in different languages) + all_uncategorized_names = Category.all_uncategorized_names + include_uncategorized = (categories & all_uncategorized_names).any? + real_categories = categories - all_uncategorized_names # Get parent category IDs for the given category names parent_category_ids = family.categories.where(name: real_categories).pluck(:id) diff --git a/test/models/transaction/search_test.rb b/test/models/transaction/search_test.rb index 164398d5a..c933c368c 100644 --- a/test/models/transaction/search_test.rb +++ b/test/models/transaction/search_test.rb @@ -494,4 +494,97 @@ class Transaction::SearchTest < ActiveSupport::TestCase # Should not match unrelated transactions assert_not_includes result_ids, no_match.entryable.id end + + test "uncategorized filter returns same results across all supported locales" do + # Create uncategorized transactions + uncategorized1 = create_transaction( + account: @checking_account, + amount: 100, + kind: "standard" + ) + + uncategorized2 = create_transaction( + account: @checking_account, + amount: 200, + kind: "standard" + ) + + # Create a categorized transaction to ensure filter is working + categorized = create_transaction( + account: @checking_account, + amount: 300, + category: categories(:food_and_drink), + kind: "standard" + ) + + # Get the expected count using English locale (known working case) + I18n.with_locale(:en) do + english_uncategorized_name = Category.uncategorized.name + english_results = Transaction::Search.new(@family, filters: { categories: [ english_uncategorized_name ] }).transactions_scope + @expected_count = english_results.count + assert_equal 2, @expected_count, "English locale should return 2 uncategorized transactions" + end + + # Test every supported locale returns the same count when filtering by that locale's uncategorized name + LanguagesHelper::SUPPORTED_LOCALES.each do |locale| + I18n.with_locale(locale) do + localized_uncategorized_name = Category.uncategorized.name + results = Transaction::Search.new(@family, filters: { categories: [ localized_uncategorized_name ] }).transactions_scope + result_count = results.count + + assert_equal @expected_count, result_count, + "Locale '#{locale}' with uncategorized name '#{localized_uncategorized_name}' should return #{@expected_count} transactions but got #{result_count}" + end + end + end + + test "uncategorized filter works with English parameter name regardless of current locale" do + # This tests the bug where URL contains English "Uncategorized" but user's locale is different + # Bug: /transactions/?q[categories][]=Uncategorized fails when locale is French + + # Create uncategorized transactions + uncategorized1 = create_transaction( + account: @checking_account, + amount: 100, + kind: "standard" + ) + + uncategorized2 = create_transaction( + account: @checking_account, + amount: 200, + kind: "standard" + ) + + # Create a categorized transaction to ensure filter is working + categorized = create_transaction( + account: @checking_account, + amount: 300, + category: categories(:food_and_drink), + kind: "standard" + ) + + # Get the English uncategorized name (this is what URLs typically contain) + english_uncategorized_name = I18n.t("models.category.uncategorized", locale: :en) + + # Get the expected count using English locale (known working case) + expected_count = nil + I18n.with_locale(:en) do + results = Transaction::Search.new(@family, filters: { categories: [ english_uncategorized_name ] }).transactions_scope + expected_count = results.count + assert_equal 2, expected_count, "English locale should return 2 uncategorized transactions" + end + + # Test that using the English parameter name works in every supported locale + # This catches the bug where French locale fails with English "Uncategorized" parameter + LanguagesHelper::SUPPORTED_LOCALES.each do |locale| + I18n.with_locale(locale) do + # Simulate URL parameter: q[categories][]=Uncategorized (English, regardless of user's locale) + results = Transaction::Search.new(@family, filters: { categories: [ english_uncategorized_name ] }).transactions_scope + result_count = results.count + + assert_equal expected_count, result_count, + "Locale '#{locale}' should return #{expected_count} transactions when filtering with English 'Uncategorized' parameter, but got #{result_count}" + end + end + end end