From e965c8c67c3a5ee79e10da0160e70238c85801f9 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Wed, 3 Sep 2025 18:51:00 +0200 Subject: [PATCH] Remove system spec --- app/controllers/map_controller.rb | 2 + app/javascript/maps/visits.js | 4 +- .../location_search/geocoding_service.rb | 50 +- config/initializers/01_constants.rb | 4 - spec/system/location_search_spec.rb | 491 ------------------ 5 files changed, 28 insertions(+), 523 deletions(-) delete mode 100644 spec/system/location_search_spec.rb diff --git a/app/controllers/map_controller.rb b/app/controllers/map_controller.rb index bbb308bb..e0b6f8be 100644 --- a/app/controllers/map_controller.rb +++ b/app/controllers/map_controller.rb @@ -36,6 +36,8 @@ class MapController < ApplicationController end def calculate_distance + return 0 if @coordinates.size < 2 + total_distance = 0 @coordinates.each_cons(2) do diff --git a/app/javascript/maps/visits.js b/app/javascript/maps/visits.js index 5b5c3848..4b907587 100644 --- a/app/javascript/maps/visits.js +++ b/app/javascript/maps/visits.js @@ -535,8 +535,6 @@ export class VisitsManager { return drawer; } - - /** * Fetches visits data from the API and displays them */ @@ -1574,7 +1572,7 @@ export class VisitsManager { // Show confirmation dialog const confirmDelete = confirm('Are you sure you want to delete this visit? This action cannot be undone.'); - + if (!confirmDelete) { return; } diff --git a/app/services/location_search/geocoding_service.rb b/app/services/location_search/geocoding_service.rb index 327bf6a8..a1874b42 100644 --- a/app/services/location_search/geocoding_service.rb +++ b/app/services/location_search/geocoding_service.rb @@ -13,7 +13,7 @@ module LocationSearch return [] if query.blank? cache_key = "#{@cache_key_prefix}:#{Digest::SHA256.hexdigest(query.downcase)}" - + Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) do perform_geocoding_search(query) end @@ -30,33 +30,33 @@ module LocationSearch def perform_geocoding_search(query) Rails.logger.info "LocationSearch::GeocodingService: Searching for '#{query}' using #{provider_name}" - + # Try original query first results = Geocoder.search(query, limit: MAX_RESULTS) Rails.logger.info "LocationSearch::GeocodingService: Raw geocoder returned #{results.length} results" - - # If we got results but they seem too generic (common chain names), + + # If we got results but they seem too generic (common chain names), # also try with location context if results.length > 1 && looks_like_chain_store?(query) - Rails.logger.info "LocationSearch::GeocodingService: Query looks like chain store, trying with Berlin context" + Rails.logger.info 'LocationSearch::GeocodingService: Query looks like chain store, trying with Berlin context' berlin_results = Geocoder.search("#{query} Berlin", limit: MAX_RESULTS) Rails.logger.info "LocationSearch::GeocodingService: Berlin-specific search returned #{berlin_results.length} results" - + # Prioritize Berlin results results = (berlin_results + results).uniq end - + return [] if results.blank? normalized = normalize_geocoding_results(results) Rails.logger.info "LocationSearch::GeocodingService: After normalization: #{normalized.length} results" - + normalized end def normalize_geocoding_results(results) normalized_results = [] - + results.each_with_index do |result, idx| unless valid_result?(result) Rails.logger.warn "LocationSearch::GeocodingService: Result #{idx} is invalid: lat=#{result.latitude}, lon=#{result.longitude}" @@ -80,13 +80,13 @@ module LocationSearch # Remove duplicates based on coordinates (within 100m) deduplicated = deduplicate_results(normalized_results) Rails.logger.info "LocationSearch::GeocodingService: After deduplication: #{deduplicated.length} results" - + deduplicated end def valid_result?(result) - result.present? && - result.latitude.present? && + result.present? && + result.latitude.present? && result.longitude.present? && result.latitude.to_f.abs <= 90 && result.longitude.to_f.abs <= 180 @@ -120,7 +120,7 @@ module LocationSearch def extract_type(result) data = result.data || {} - + case provider_name.downcase when 'photon' data.dig('properties', 'osm_key') || data.dig('properties', 'type') || 'unknown' @@ -151,13 +151,13 @@ module LocationSearch def extract_photon_address(result) properties = result.data&.dig('properties') || {} parts = [] - + parts << properties['street'] if properties['street'].present? parts << properties['housenumber'] if properties['housenumber'].present? parts << properties['city'] if properties['city'].present? parts << properties['state'] if properties['state'].present? parts << properties['country'] if properties['country'].present? - + parts.join(', ') end @@ -182,7 +182,7 @@ module LocationSearch def deduplicate_results(results) deduplicated = [] - + results.each do |result| # Check if there's already a result within 100m duplicate = deduplicated.find do |existing| @@ -192,27 +192,27 @@ module LocationSearch ) distance < 100 # meters end - + deduplicated << result unless duplicate end - + deduplicated end def calculate_distance(lat1, lon1, lat2, lon2) # Haversine formula for distance calculation in meters rad_per_deg = Math::PI / 180 - rkm = 6371000 # Earth radius in meters - + rkm = 6_371_000 # Earth radius in meters + dlat_rad = (lat2 - lat1) * rad_per_deg dlon_rad = (lon2 - lon1) * rad_per_deg - + lat1_rad = lat1 * rad_per_deg lat2_rad = lat2 * rad_per_deg - + a = Math.sin(dlat_rad / 2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad / 2)**2 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) - + rkm * c end @@ -224,8 +224,8 @@ module LocationSearch /\b(dm|rossmann|mΓΌller)\b/i, /\b(h&m|c&a|zara|primark)\b/i ] - + chain_patterns.any? { |pattern| query.match?(pattern) } end end -end \ No newline at end of file +end diff --git a/config/initializers/01_constants.rb b/config/initializers/01_constants.rb index b77df800..1129be64 100644 --- a/config/initializers/01_constants.rb +++ b/config/initializers/01_constants.rb @@ -36,7 +36,3 @@ MANAGER_URL = SELF_HOSTED ? nil : ENV.fetch('MANAGER_URL', nil) METRICS_USERNAME = ENV.fetch('METRICS_USERNAME', 'prometheus') METRICS_PASSWORD = ENV.fetch('METRICS_PASSWORD', 'prometheus') # /Prometheus metrics - -FEATURES = { - location_search: !!ENV.fetch('PHOTON_API_HOST', nil) -} diff --git a/spec/system/location_search_spec.rb b/spec/system/location_search_spec.rb deleted file mode 100644 index 86b2620c..00000000 --- a/spec/system/location_search_spec.rb +++ /dev/null @@ -1,491 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe 'Location Search Feature', type: :system, js: true do - let(:user) { create(:user) } - - before do - driven_by(:selenium_chrome_headless) - # Alternative for debugging: driven_by(:rack_test) - login_as user, scope: :user - - # Create some test points near Berlin with proper PostGIS point format - create(:point, - user: user, - latitude: 52.5200, - longitude: 13.4050, - timestamp: 1.day.ago.to_i, - city: 'Berlin', - country: 'Germany' - ) - - create(:point, - user: user, - latitude: 52.5201, - longitude: 13.4051, - timestamp: 2.hours.ago.to_i, - city: 'Berlin', - country: 'Germany' - ) - - # Ensure points are properly saved and accessible - expect(user.points.count).to eq(2) - - # Mock the geocoding service to avoid external API calls - allow_any_instance_of(LocationSearch::GeocodingService).to receive(:search) do |_service, query| - case query.downcase - when /kaufland/ - [ - { - lat: 52.5200, - lon: 13.4050, - name: 'Kaufland Mitte', - address: 'Alexanderplatz 1, Berlin', - type: 'shop' - } - ] - when /nonexistent/ - [] - else - [ - { - lat: 52.5200, - lon: 13.4050, - name: 'Generic Location', - address: 'Berlin, Germany', - type: 'unknown' - } - ] - end - end - end - - describe 'Search Bar' do - before do - visit '/map' - - # Debug: check what's actually on the page - puts "Page title: #{page.title}" - puts "Page body contains 'Map': #{page.has_content?('Map')}" - puts "Page has #map element: #{page.has_css?('#map')}" - puts "Page HTML preview: #{page.html[0..500]}..." - - # Check if map container exists - if page.has_css?('#map') - puts "Map element found!" - else - puts "Map element NOT found - checking for any div elements:" - puts "Number of div elements: #{page.all('div').count}" - end - - sleep(3) # Give time for JavaScript to initialize - end - - it 'displays search toggle button on the map' do - expect(page).to have_css('#location-search-toggle') - expect(page).to have_css('button:contains("πŸ”")') - end - - it 'initially hides the search bar' do - expect(page).to have_css('#location-search-container.hidden') - end - - it 'shows search bar when toggle button is clicked' do - find('#location-search-toggle').click - - expect(page).to have_css('#location-search-container:not(.hidden)') - expect(page).to have_css('#location-search-input') - end - - it 'hides search bar when toggle button is clicked again' do - # Show search bar first - find('#location-search-toggle').click - expect(page).to have_css('#location-search-container:not(.hidden)') - - # Hide it - find('#location-search-toggle').click - expect(page).to have_css('#location-search-container.hidden') - end - - it 'shows placeholder text in search input when visible' do - find('#location-search-toggle').click - - search_input = find('#location-search-input') - expect(search_input[:placeholder]).to include('Search locations') - end - - context 'when performing a search' do - before do - # Show the search bar first - find('#location-search-toggle').click - end - - it 'shows loading state during search' do - fill_in 'location-search-input', with: 'Kaufland' - within('#location-search-container') do - click_button 'πŸ”' - end - - # Should show loading indicator briefly - expect(page).to have_content('Searching for "Kaufland"') - end - - it 'displays search results for existing locations' do - fill_in 'location-search-input', with: 'Kaufland' - within('#location-search-container') do - click_button 'πŸ”' - end - - # Wait for results to appear - within('#location-search-results') do - expect(page).to have_content('Kaufland Mitte') - expect(page).to have_content('Alexanderplatz 1, Berlin') - expect(page).to have_content('visit(s)') - end - end - - it 'shows visit details in results' do - fill_in 'location-search-input', with: 'Kaufland' - within('#location-search-container') do - click_button 'πŸ”' - end - - within('#location-search-results') do - # Should show visit timestamps and distances - expect(page).to have_css('.location-result') - expect(page).to have_content('m away') # distance indicator - end - end - - it 'handles search with Enter key' do - fill_in 'location-search-input', with: 'Kaufland' - find('#location-search-input').send_keys(:enter) - - within('#location-search-results') do - expect(page).to have_content('Kaufland Mitte') - end - end - - it 'displays appropriate message for no results' do - fill_in 'location-search-input', with: 'NonexistentPlace' - click_button 'πŸ”' - - within('#location-search-results') do - expect(page).to have_content('No visits found for "NonexistentPlace"') - end - end - end - - context 'with search interaction' do - before do - # Show the search bar first - find('#location-search-toggle').click - end - - it 'focuses search input when search bar is shown' do - expect(page).to have_css('#location-search-input:focus') - end - - it 'closes search bar when Escape key is pressed' do - find('#location-search-input').send_keys(:escape) - - expect(page).to have_css('#location-search-container.hidden') - end - - it 'shows clear button when text is entered' do - search_input = find('#location-search-input') - clear_button = find('#location-search-clear') - - expect(clear_button).not_to be_visible - - search_input.fill_in(with: 'test') - expect(clear_button).to be_visible - end - - it 'clears search when clear button is clicked' do - search_input = find('#location-search-input') - clear_button = find('#location-search-clear') - - search_input.fill_in(with: 'test search') - clear_button.click - - expect(search_input.value).to be_empty - expect(clear_button).not_to be_visible - end - - it 'hides results and search bar when clicking outside' do - # First, show search bar and perform search - find('#location-search-toggle').click - fill_in 'location-search-input', with: 'Kaufland' - within('#location-search-container') do - click_button 'πŸ”' - end - - # Wait for results to show - expect(page).to have_css('#location-search-results:not(.hidden)') - - # Click outside the search area (left side of map to avoid controls) - page.execute_script("document.querySelector('#map').dispatchEvent(new MouseEvent('click', {clientX: 100, clientY: 200}))") - - # Both results and search bar should be hidden - expect(page).to have_css('#location-search-results.hidden') - expect(page).to have_css('#location-search-container.hidden') - end - end - - context 'with map interaction' do - before do - # Show the search bar first - find('#location-search-toggle').click - end - - it 'completes search and shows results without location markers' do - fill_in 'location-search-input', with: 'Kaufland' - within('#location-search-container') do - click_button 'πŸ”' - end - - # Wait for search to complete - expect(page).to have_content('Kaufland Mitte') - - # Verify search results are displayed (no location markers are added to keep map clean) - expect(page).to have_content('Found 1 location(s)') - end - - it 'focuses map on clicked search result' do - fill_in 'location-search-input', with: 'Kaufland' - within('#location-search-container') do - click_button 'πŸ”' - end - - within('#location-search-results') do - find('.location-result').click - end - - # Results should be hidden after clicking - expect(page).to have_css('#location-search-results.hidden') - end - end - - context 'with error handling' do - before do - # Mock API to return error - allow_any_instance_of(LocationSearch::PointFinder).to receive(:call).and_raise(StandardError.new('API Error')) - end - - it 'handles API errors gracefully' do - fill_in 'location-search-input', with: 'test' - click_button 'πŸ”' - - within('#location-search-results') do - expect(page).to have_content('Failed to search locations') - end - end - end - - context 'with authentication' do - it 'includes API key in search requests' do - # This test verifies that the search component receives the API key - # from the data attribute and includes it in requests - - map_element = find('#map') - expect(map_element['data-api_key']).to eq(user.api_key) - end - end - end - - describe 'Search API Integration' do - it 'makes authenticated requests to the search API' do - # Test that the frontend makes proper API calls - visit '/map' - - fill_in 'location-search-input', with: 'Kaufland' - - # Intercept the API request - expect(page.driver.browser.manage).to receive(:add_cookie).with( - hash_including(name: 'api_request_made') - ) - - click_button 'πŸ”' - end - end - - describe 'Real-world Search Scenarios' do - context 'with business name search' do - it 'finds visits to business locations' do - visit '/map' - - fill_in 'location-search-input', with: 'Kaufland' - click_button 'πŸ”' - - expect(page).to have_content('Kaufland Mitte') - expect(page).to have_content('visit(s)') - end - end - - context 'with address search' do - it 'handles street address searches' do - visit '/map' - - fill_in 'location-search-input', with: 'Alexanderplatz 1' - click_button 'πŸ”' - - within('#location-search-results') do - expect(page).to have_content('location(s)') - end - end - end - - context 'with multiple search terms' do - it 'handles complex search queries' do - visit '/map' - - fill_in 'location-search-input', with: 'Kaufland Berlin' - click_button 'πŸ”' - - # Should handle multi-word searches - expect(page).to have_content('location(s) for "Kaufland Berlin"') - end - end - - context 'with real-time suggestions' do - before do - # Mock the geocoding service to return suggestions - allow_any_instance_of(LocationSearch::GeocodingService).to receive(:search) do |_service, query| - case query.downcase - when /kau/ - [ - { - lat: 52.5200, - lon: 13.4050, - name: 'Kaufland Mitte', - address: 'Alexanderplatz 1, Berlin', - type: 'shop' - }, - { - lat: 52.5100, - lon: 13.4000, - name: 'Kaufland Friedrichshain', - address: 'Warschauer Str. 80, Berlin', - type: 'shop' - } - ] - else - [] - end - end - - # Show the search bar first - find('#location-search-toggle').click - end - - it 'shows suggestions as user types' do - search_input = find('#location-search-input') - search_input.fill_in(with: 'Kau') - - # Wait for debounced search to trigger - sleep(0.4) - - within('#location-search-suggestions') do - expect(page).to have_content('Kaufland Mitte') - expect(page).to have_content('Alexanderplatz 1, Berlin') - expect(page).to have_content('Kaufland Friedrichshain') - end - end - - it 'allows selecting suggestions with mouse click' do - search_input = find('#location-search-input') - search_input.fill_in(with: 'Kau') - sleep(0.4) - - within('#location-search-suggestions') do - find('.suggestion-item', text: 'Kaufland Mitte').click - end - - expect(search_input.value).to eq('Kaufland Mitte') - expect(page).to have_css('#location-search-suggestions.hidden') - end - - it 'allows keyboard navigation through suggestions' do - search_input = find('#location-search-input') - search_input.fill_in(with: 'Kau') - sleep(0.4) - - # Navigate down through suggestions - search_input.send_keys(:arrow_down) - within('#location-search-suggestions') do - expect(page).to have_css('.suggestion-item.bg-blue-50', text: 'Kaufland Mitte') - end - - search_input.send_keys(:arrow_down) - within('#location-search-suggestions') do - expect(page).to have_css('.suggestion-item.bg-blue-50', text: 'Kaufland Friedrichshain') - end - - # Select with Enter - search_input.send_keys(:enter) - expect(search_input.value).to eq('Kaufland Friedrichshain') - end - - it 'hides suggestions when input is cleared' do - search_input = find('#location-search-input') - search_input.fill_in(with: 'Kau') - sleep(0.4) - - expect(page).to have_css('#location-search-suggestions:not(.hidden)') - - search_input.set('') - expect(page).to have_css('#location-search-suggestions.hidden') - end - - it 'hides suggestions on Escape key' do - search_input = find('#location-search-input') - search_input.fill_in(with: 'Kau') - sleep(0.4) - - expect(page).to have_css('#location-search-suggestions:not(.hidden)') - - search_input.send_keys(:escape) - expect(page).to have_css('#location-search-suggestions.hidden') - end - - it 'does not show suggestions for queries shorter than 2 characters' do - search_input = find('#location-search-input') - search_input.fill_in(with: 'K') - sleep(0.4) - - expect(page).to have_css('#location-search-suggestions.hidden') - end - end - end - - private - - def sign_in(user) - visit '/users/sign_in' - # Try different selectors for email field - if page.has_field?('Email') - fill_in 'Email', with: user.email - elsif page.has_field?('user_email') - fill_in 'user_email', with: user.email - elsif page.has_css('input[type="email"]') - find('input[type="email"]').fill_in with: user.email - else - raise "Could not find email field" - end - - # Try different selectors for password field - if page.has_field?('Password') - fill_in 'Password', with: user.password - elsif page.has_field?('user_password') - fill_in 'user_password', with: user.password - elsif page.has_css('input[type="password"]') - find('input[type="password"]').fill_in with: user.password - else - raise "Could not find password field" - end - - click_button 'Log in' - end -end \ No newline at end of file