Back to Repositories

Testing Transaction Management System Integration in Maybe Finance

This comprehensive test suite validates transaction management functionality in the Maybe Finance application, covering search, filtering, selection, and creation of financial transactions. The tests ensure robust handling of transaction operations across different account types and user interactions.

Test Coverage Overview

The test suite provides extensive coverage of transaction management features:

  • Transaction search and filtering capabilities
  • Batch selection and deselection of transactions
  • Group-based transaction handling
  • Investment account deposit operations
  • Edge cases for transaction filtering and empty states

Implementation Analysis

The testing approach utilizes Minitest’s system testing framework with a focus on user interaction simulation. The implementation employs DOM-based assertions and complex setup fixtures to validate transaction management workflows. Key patterns include hierarchical selection handling and comprehensive filter combinations.

The tests leverage Rails’ system testing capabilities for realistic browser-based interaction testing.

Technical Details

Testing tools and configuration:

  • ApplicationSystemTestCase for system-level testing
  • Fixture-based test data setup
  • DOM manipulation and form interaction testing
  • Custom helper methods for transaction creation and validation
  • Page size configuration for pagination testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive setup and teardown management
  • Isolated test scenarios with clear assertions
  • Helper method abstraction for common operations
  • Consistent naming conventions and organization
  • Thorough validation of user interface elements and interactions

maybe-finance/maybe

test/system/transactions_test.rb

            
require "application_system_test_case"

class TransactionsTest < ApplicationSystemTestCase
  setup do
    sign_in @user = users(:family_admin)

    Account::Entry.delete_all # clean slate

    @uncategorized_transaction = create_transaction("one", 12.days.ago.to_date, 100)
    create_transaction("two", 10.days.ago.to_date, 100)
    create_transaction("three", 9.days.ago.to_date, 100)
    create_transaction("four", 8.days.ago.to_date, 100)
    create_transaction("five", 7.days.ago.to_date, 100)
    create_transaction("six", 7.days.ago.to_date, 100)
    create_transaction("seven", 4.days.ago.to_date, 100)
    create_transaction("eight", 3.days.ago.to_date, 100)
    create_transaction("nine", 1.days.ago.to_date, 100)
    create_transaction("ten", 1.days.ago.to_date, 100)
    create_transaction("eleven", Date.current, 100, category: categories(:food_and_drink), tags: [ tags(:one) ], merchant: merchants(:amazon))

    @transactions = @user.family.entries
                         .account_transactions
                         .reverse_chronological

    @transaction = @transactions.first

    @page_size = 10

    visit transactions_url(per_page: @page_size)
  end

  test "can search for a transaction" do
    assert_selector "h1", text: "Transactions"

    within "form#transactions-search" do
      fill_in "Search transactions by name", with: @transaction.name
    end

    assert_selector "#" + dom_id(@transaction), count: 1

    within "#transaction-search-filters" do
      assert_text @transaction.name
    end
  end

  test "can open filters and apply one or more" do
    find("#transaction-filters-button").click

    within "#transaction-filters-menu" do
      check(@transaction.account.name)
      click_button "Category"
      check(@transaction.account_transaction.category.name)
      click_button "Apply"
    end

    assert_selector "#" + dom_id(@transaction), count: 1

    within "#transaction-search-filters" do
      assert_text @transaction.account.name
      assert_text @transaction.account_transaction.category.name
    end
  end

  test "can filter uncategorized transactions" do
    find("#transaction-filters-button").click

    within "#transaction-filters-menu" do
      click_button "Category"
      check("Uncategorized")
      click_button "Apply"
    end

    assert_selector "#" + dom_id(@uncategorized_transaction), count: 1
    assert_no_selector("#" + dom_id(@transaction))

    find("#transaction-filters-button").click

    within "#transaction-filters-menu" do
      click_button "Category"
      check(@transaction.account_transaction.category.name)
      click_button "Apply"
    end

    assert_selector "#" + dom_id(@transaction), count: 1
    assert_selector "#" + dom_id(@uncategorized_transaction), count: 1
  end

  test "all filters work and empty state shows if no match" do
    find("#transaction-filters-button").click

    account = @transaction.account
    category = @transaction.account_transaction.category
    merchant = @transaction.account_transaction.merchant

    within "#transaction-filters-menu" do
      click_button "Account"
      check(account.name)

      click_button "Date"
      fill_in "q_start_date", with: 10.days.ago.to_date
      fill_in "q_end_date", with: 1.day.ago.to_date

      click_button "Type"
      check("Income")

      click_button "Amount"
      select "Less than"
      fill_in "q_amount", with: 200

      click_button "Category"
      check(category.name)

      click_button "Merchant"
      check(merchant.name)

      click_button "Apply"
    end

    assert_text "No entries found"

    # Page reload doesn't affect results
    visit current_url

    assert_text "No entries found"

    within "ul#transaction-search-filters" do
      find("li", text: account.name).first("a").click
      find("li", text: "on or after #{10.days.ago.to_date}").first("a").click
      find("li", text: "on or before #{1.day.ago.to_date}").first("a").click
      find("li", text: "Income").first("a").click
      find("li", text: "less than 200").first("a").click
      find("li", text: category.name).first("a").click
      find("li", text: merchant.name).first("a").click
    end

    assert_selector "#" + dom_id(@transaction), count: 1
  end

  test "can select and deselect entire page of transactions" do
    all_transactions_checkbox.check
    assert_selection_count(number_of_transactions_on_page)
    all_transactions_checkbox.uncheck
    assert_selection_count(0)
  end

  test "can select and deselect groups of transactions" do
    date_transactions_checkbox(1.day.ago.to_date).check
    assert_selection_count(2)

    date_transactions_checkbox(1.day.ago.to_date).uncheck
    assert_selection_count(0)
  end

  test "can select and deselect individual transactions" do
    transaction_checkbox(@transactions.first).check
    assert_selection_count(1)
    transaction_checkbox(@transactions.second).check
    assert_selection_count(2)
    transaction_checkbox(@transactions.second).uncheck
    assert_selection_count(1)
  end

  test "outermost group always overrides inner selections" do
    transaction_checkbox(@transactions.first).check
    assert_selection_count(1)

    all_transactions_checkbox.check
    assert_selection_count(number_of_transactions_on_page)

    transaction_checkbox(@transactions.first).uncheck
    assert_selection_count(number_of_transactions_on_page - 1)

    date_transactions_checkbox(1.day.ago.to_date).uncheck
    assert_selection_count(number_of_transactions_on_page - 3)

    all_transactions_checkbox.uncheck
    assert_selection_count(0)
  end


  test "can create deposit transaction for investment account" do
    investment_account = accounts(:investment)
    investment_account.entries.create!(name: "Investment account", date: Date.current, amount: 1000, currency: "USD", entryable: Account::Transaction.new)
    transfer_date = Date.current
    visit account_url(investment_account, tab: "activity")
    within "[data-testid='activity-menu']" do
      click_on "New"
      click_on "New transaction"
    end
    select "Deposit", from: "Type"
    fill_in "Date", with: transfer_date
    fill_in "account_entry[amount]", with: 175.25
    click_button "Add transaction"
    within "#entry-group-" + transfer_date.to_s do
      assert_text "175.25"
    end
  end

  private

    def create_transaction(name, date, amount, category: nil, merchant: nil, tags: [])
      account = accounts(:depository)

      account.entries.create! \
        name: name,
        date: date,
        amount: amount,
        currency: "USD",
        entryable: Account::Transaction.new(category: category, merchant: merchant, tags: tags)
    end

    def number_of_transactions_on_page
      [ @user.family.entries.without_transfers.count, @page_size ].min
    end

    def all_transactions_checkbox
      find("#selection_entry")
    end

    def date_transactions_checkbox(date)
      find("#selection_entry_#{date}")
    end

    def transaction_checkbox(transaction)
      find("#" + dom_id(transaction, "selection"))
    end

    def assert_selection_count(count)
      if count == 0
        assert_no_selector("#entry-selection-bar")
      else
        within "#entry-selection-bar" do
          assert_text "#{count} transaction#{count == 1 ? "" : "s"} selected"
        end
      end
    end
end