diff --git a/test/controllers/splits_controller_test.rb b/test/controllers/splits_controller_test.rb index 5cb89a9ff..412ef7aef 100644 --- a/test/controllers/splits_controller_test.rb +++ b/test/controllers/splits_controller_test.rb @@ -17,6 +17,21 @@ class SplitsControllerTest < ActionDispatch::IntegrationTest assert_response :success end + test "new groups subcategories immediately after their parent in the category selector" do + get new_transaction_split_path(@entry) + assert_response :success + + doc = Nokogiri::HTML::Document.parse(response.body) + category_values = doc.css(".category-select-container [data-value]").map { |node| node["data-value"] } + + parent_index = category_values.index(categories(:food_and_drink).id) + child_index = category_values.index(categories(:subcategory).id) + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index + end + test "create with valid params splits transaction" do assert_difference "Entry.count", 2 do post transaction_split_path(@entry), params: { diff --git a/test/controllers/transactions/categorizes_controller_test.rb b/test/controllers/transactions/categorizes_controller_test.rb index 1e74d8bbb..8c40f3286 100644 --- a/test/controllers/transactions/categorizes_controller_test.rb +++ b/test/controllers/transactions/categorizes_controller_test.rb @@ -26,6 +26,23 @@ class Transactions::CategorizesControllerTest < ActionDispatch::IntegrationTest assert_response :success end + test "show groups subcategories immediately after their parent in the category select" do + create_transaction(account: @account, name: "Starbucks") + get transactions_categorize_url + + assert_response :success + + doc = Nokogiri::HTML::Document.parse(response.body) + option_values = doc.css("select[name='category_id'] option").map { |node| node["value"] } + + parent_index = option_values.index(categories(:food_and_drink).id) + child_index = option_values.index(categories(:subcategory).id) + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index + end + test "show renders full dates so multi-year lists are unambiguous" do create_transaction(account: @account, name: "Starbucks", date: Date.new(2024, 7, 8)) diff --git a/test/controllers/transactions_controller_test.rb b/test/controllers/transactions_controller_test.rb index b533d89c7..0be05a1af 100644 --- a/test/controllers/transactions_controller_test.rb +++ b/test/controllers/transactions_controller_test.rb @@ -8,6 +8,21 @@ class TransactionsControllerTest < ActionDispatch::IntegrationTest @entry = entries(:transaction) end + test "index groups subcategories immediately after their parent in the category filter" do + get transactions_url + assert_response :success + + doc = Nokogiri::HTML::Document.parse(response.body) + checkbox_values = doc.css("input[name='q[categories][]']").map { |node| node["value"] } + + parent_index = checkbox_values.index(categories(:food_and_drink).name) + child_index = checkbox_values.index(categories(:subcategory).name) + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index + end + test "creates with transaction details" do assert_difference [ "Entry.count", "Transaction.count" ], 1 do post transactions_url, params: { @@ -467,6 +482,37 @@ end assert_not entry.protected_from_sync? end + test "new groups subcategories immediately after their parent in the category select" do + get new_transaction_url + assert_response :success + + doc = Nokogiri::HTML::Document.parse(response.body) + trigger = doc.at_css("#category_id_trigger") + assert_not_nil trigger, "expected the category select trigger button to render" + + wrapper = trigger.ancestors(".relative").first + category_values = wrapper.css("[data-value]").map { |node| node["data-value"] } + + parent_index = category_values.index(categories(:food_and_drink).id) + child_index = category_values.index(categories(:subcategory).id) + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index + end + + test "new renders a search box for account selection" do + get new_transaction_url + assert_response :success + + doc = Nokogiri::HTML::Document.parse(response.body) + trigger = doc.at_css("#account_id_trigger") + assert_not_nil trigger, "expected the account select trigger button to render" + + wrapper = trigger.ancestors(".relative").first + assert_not_nil wrapper.at_css("input[type='search']"), "expected a search input inside the account select" + end + test "new with duplicate_entry_id pre-fills form from source transaction" do @entry.reload diff --git a/test/models/category/group_test.rb b/test/models/category/group_test.rb new file mode 100644 index 000000000..cfe366e3e --- /dev/null +++ b/test/models/category/group_test.rb @@ -0,0 +1,52 @@ +require "test_helper" + +class Category::GroupTest < ActiveSupport::TestCase + setup do + @family = families(:dylan_family) + end + + test "for groups categories by parent, root categories without children get an empty subcategories array" do + groups = Category::Group.for(@family.categories) + + food_group = groups.find { |g| g.category == categories(:food_and_drink) } + assert_equal [ categories(:subcategory) ], food_group.subcategories + + income_group = groups.find { |g| g.category == categories(:income) } + assert_equal [], income_group.subcategories + end + + test "for does not include a category as its own root when it is a subcategory" do + groups = Category::Group.for(@family.categories) + + assert_nil groups.find { |g| g.category == categories(:subcategory) } + end + + test "select_options orders parent immediately followed by its children" do + options = Category::Group.select_options(@family.categories) + + parent_index = options.index { |label, id| id == categories(:food_and_drink).id } + child_index = options.index { |label, id| id == categories(:subcategory).id } + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index, "subcategory should immediately follow its parent in the options list" + end + + test "select_options indents subcategory labels so they're visually distinguishable from parents" do + options = Category::Group.select_options(@family.categories) + + parent_label, = options.find { |label, id| id == categories(:food_and_drink).id } + child_label, = options.find { |label, id| id == categories(:subcategory).id } + + assert_equal categories(:food_and_drink).name, parent_label + assert_not_equal categories(:subcategory).name, child_label + assert_includes child_label, categories(:subcategory).name + end + + test "select_options returns [label, id] pairs usable directly by options_for_select / form.select" do + options = Category::Group.select_options(@family.categories) + + assert options.all? { |pair| pair.is_a?(Array) && pair.size == 2 } + assert options.all? { |_label, id| id.present? } + end +end diff --git a/test/models/category_test.rb b/test/models/category_test.rb index a12b9790a..7ef173b11 100644 --- a/test/models/category_test.rb +++ b/test/models/category_test.rb @@ -159,4 +159,15 @@ class CategoryTest < ActiveSupport::TestCase assert lookup.key?(category.id) assert_not lookup.key?(0) end + + test "alphabetically_by_hierarchy orders a category's subcategories immediately after it" do + ordered = @family.categories.alphabetically_by_hierarchy.to_a + + parent_index = ordered.index(categories(:food_and_drink)) + child_index = ordered.index(categories(:subcategory)) + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index + end end diff --git a/test/models/import/category_mapping_test.rb b/test/models/import/category_mapping_test.rb new file mode 100644 index 000000000..4d20966b1 --- /dev/null +++ b/test/models/import/category_mapping_test.rb @@ -0,0 +1,25 @@ +require "test_helper" + +class Import::CategoryMappingTest < ActiveSupport::TestCase + setup do + @mapping = import_mappings(:one) + end + + test "selectable_values groups subcategories immediately after their parent" do + options = @mapping.selectable_values + + parent_index = options.index { |_label, id| id == categories(:food_and_drink).id } + child_index = options.index { |_label, id| id == categories(:subcategory).id } + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index + end + + test "selectable_values still prepends 'Add as new category' when key is present" do + options = @mapping.selectable_values + + assert_equal "Add as new category", options.first.first + assert_equal Import::Mapping::CREATE_NEW_KEY, options.first.last + end +end diff --git a/test/models/rule/action_executor/set_transaction_category_test.rb b/test/models/rule/action_executor/set_transaction_category_test.rb new file mode 100644 index 000000000..404a93198 --- /dev/null +++ b/test/models/rule/action_executor/set_transaction_category_test.rb @@ -0,0 +1,19 @@ +require "test_helper" + +class Rule::ActionExecutor::SetTransactionCategoryTest < ActiveSupport::TestCase + setup do + @rule = rules(:one) + @executor = Rule::ActionExecutor::SetTransactionCategory.new(@rule) + end + + test "options groups subcategories immediately after their parent" do + options = @executor.options + + parent_index = options.index { |_label, id| id == categories(:food_and_drink).id } + child_index = options.index { |_label, id| id == categories(:subcategory).id } + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index + end +end diff --git a/test/models/rule/condition_filter/transaction_category_test.rb b/test/models/rule/condition_filter/transaction_category_test.rb new file mode 100644 index 000000000..9569bc050 --- /dev/null +++ b/test/models/rule/condition_filter/transaction_category_test.rb @@ -0,0 +1,28 @@ +require "test_helper" + +class Rule::ConditionFilter::TransactionCategoryTest < ActiveSupport::TestCase + setup do + @rule = rules(:one) + @filter = Rule::ConditionFilter::TransactionCategory.new(@rule) + end + + test "options groups subcategories immediately after their parent" do + options = @filter.options + + parent_index = options.index { |_label, id| id == categories(:food_and_drink).id } + child_index = options.index { |_label, id| id == categories(:subcategory).id } + + assert_not_nil parent_index + assert_not_nil child_index + assert_equal parent_index + 1, child_index + end + + test "options indents subcategory labels" do + options = @filter.options + + child_label, = options.find { |_label, id| id == categories(:subcategory).id } + + assert_not_equal categories(:subcategory).name, child_label + assert_includes child_label, categories(:subcategory).name + end +end