Back to Repositories

Testing BelongsToAssociation Field Integration in Rails Admin

This test suite validates the BelongsToAssociation field functionality in Rails Admin, focusing on handling relationships between models. It covers various scenarios including create, show, and edit operations with different association configurations.

Test Coverage Overview

The test suite provides comprehensive coverage of BelongsToAssociation field functionality:
  • Basic association creation and display
  • Default value handling in associations
  • Custom primary key associations
  • Composite foreign key relationships
  • Remote-sourced field interactions
  • Nested association fields

Implementation Analysis

The testing approach utilizes RSpec request specs with page object interactions. It implements factory-based test data generation using FactoryBot and leverages RSpec’s expectation syntax for assertions. The tests cover both standard HTTP requests and JavaScript-enabled scenarios using Capybara.

Technical Details

Testing tools and configuration:
  • RSpec for test framework
  • Capybara for browser simulation
  • FactoryBot for test data generation
  • Custom RailsAdmin configuration blocks
  • JavaScript-enabled tests for AJAX interactions

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Isolated test contexts with proper setup and teardown
  • Comprehensive edge case coverage
  • Clear test organization with descriptive contexts
  • Effective use of shared examples and before blocks
  • Proper handling of asynchronous operations

railsadminteam/rails_admin

spec/integration/fields/belongs_to_association_spec.rb

            
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'BelongsToAssociation field', type: :request do
  subject { page }

  it 'does not add a related id to the belongs_to create team link' do
    @player = FactoryBot.create :player
    visit edit_path(model_name: 'player', id: @player.id)
    is_expected.to have_selector("a[data-link='/admin/team/new?modal=true']")
  end

  describe 'on create' do
    let!(:draft) { FactoryBot.create :draft }
    let(:team) { FactoryBot.create :team }

    it 'shows selects' do
      visit new_path(model_name: 'player')
      is_expected.to have_selector('select#player_team_id')
    end

    context 'with default_value' do
      before do
        id = team.id
        RailsAdmin.config Player do
          configure :team do
            default_value id
          end
        end
      end

      it 'shows the value as selected' do
        visit new_path(model_name: 'player')
        expect(find('select#player_team_id').value).to eq team.id.to_s
      end
    end
  end

  describe 'on show' do
    before do
      @team   = FactoryBot.create :team
      @player = FactoryBot.create :player, team_id: @team.id
      visit show_path(model_name: 'player', id: @player.id)
    end

    it 'shows associated objects' do
      is_expected.to have_css("a[href='/admin/team/#{@team.id}']")
    end
  end

  context 'with custom primary_key option' do
    let(:users) { FactoryBot.create_list :managing_user, 2 }
    let!(:teams) { [FactoryBot.create(:managed_team, manager: users[0].email), FactoryBot.create(:managed_team)] }
    before do
      RailsAdmin.config.included_models = [ManagedTeam, ManagingUser]
      RailsAdmin.config ManagedTeam do
        field :user
      end
    end

    it 'allows update' do
      visit edit_path(model_name: 'managed_team', id: teams[0].id)
      expect(page).to have_css("select#managed_team_manager option[value=\"#{users[0].email}\"]")
      select("ManagingUser ##{users[1].id}", from: 'User')
      click_button 'Save'
      teams[0].reload
      expect(teams[0].user).to eq users[1]
    end

    context 'when fetching associated objects via xhr' do
      before do
        RailsAdmin.config ManagedTeam do
          field(:user) { associated_collection_cache_all false }
        end
      end

      it 'allows update', js: true do
        visit edit_path(model_name: 'managed_team', id: teams[0].id)
        find('input.ra-filtering-select-input').set('M')
        page.execute_script("document.querySelector('input.ra-filtering-select-input').dispatchEvent(new KeyboardEvent('keydown'))")
        expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item a')
        page.execute_script %{[...document.querySelectorAll('ul.ui-autocomplete li.ui-menu-item')].find(e => e.innerText.includes("ManagingUser ##{users[1].id}")).click()}
        click_button 'Save'
        teams[0].reload
        expect(teams[0].user).to eq users[1]
      end
    end
  end

  context 'with composite foreign keys', composite_primary_keys: true do
    let!(:fanship) { FactoryBot.create(:fanship) }
    let(:favorite_player) { FactoryBot.create(:favorite_player) }

    describe 'via default field' do
      it 'allows update' do
        visit edit_path(model_name: 'favorite_player', id: favorite_player.id)
        is_expected.to have_select('Fanship', selected: "Fanship ##{favorite_player.fanship.id}")
        select("Fanship ##{fanship.id}", from: 'Fanship')
        click_button 'Save'
        is_expected.to have_content 'Favorite player successfully updated'
        expect(FavoritePlayer.all.map(&:fanship)).to eq [fanship]
      end

      context 'with invalid key' do
        before do
          allow_any_instance_of(RailsAdmin::Config::Fields::Types::BelongsToAssociation).
            to receive(:collection).and_return([["Fanship ##{fanship.id}", 'invalid']])
        end

        it 'fails to update' do
          visit edit_path(model_name: 'favorite_player', id: favorite_player.id)
          select("Fanship ##{fanship.id}", from: 'Fanship')
          click_button 'Save'
          is_expected.to have_content 'Fanship must exist'
        end
      end
    end

    describe 'via remote-sourced field' do
      before do
        RailsAdmin.config FavoritePlayer do
          field :fanship do
            associated_collection_cache_all false
          end
        end
      end

      it 'allows update', js: true do
        visit edit_path(model_name: 'favorite_player', id: favorite_player.id)
        find('.fanship_field input.ra-filtering-select-input').set(fanship.fan_id)
        page.execute_script("document.querySelector('.fanship_field input.ra-filtering-select-input').dispatchEvent(new KeyboardEvent('keydown'))")
        expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item a')
        page.execute_script %{[...document.querySelectorAll('ul.ui-autocomplete li.ui-menu-item')].find(e => e.innerText.includes("Fanship ##{fanship.id}")).click()}
        click_button 'Save'
        is_expected.to have_content 'Favorite player successfully updated'
        expect(FavoritePlayer.all.map(&:fanship)).to eq [fanship]
      end
    end

    describe 'via nested field' do
      it 'allows update' do
        visit edit_path(model_name: 'nested_favorite_player', id: favorite_player.id)
        fill_in 'Since', with: '2020-01-23'
        click_button 'Save'
        is_expected.to have_content 'Nested favorite player successfully updated'
        expect(favorite_player.reload.fanship.since).to eq Date.new(2020, 1, 23)
      end
    end
  end
end