mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
Remove system spec
This commit is contained in:
parent
e3e5770843
commit
e965c8c67c
5 changed files with 28 additions and 523 deletions
|
|
@ -36,6 +36,8 @@ class MapController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_distance
|
def calculate_distance
|
||||||
|
return 0 if @coordinates.size < 2
|
||||||
|
|
||||||
total_distance = 0
|
total_distance = 0
|
||||||
|
|
||||||
@coordinates.each_cons(2) do
|
@coordinates.each_cons(2) do
|
||||||
|
|
|
||||||
|
|
@ -535,8 +535,6 @@ export class VisitsManager {
|
||||||
return drawer;
|
return drawer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches visits data from the API and displays them
|
* Fetches visits data from the API and displays them
|
||||||
*/
|
*/
|
||||||
|
|
@ -1574,7 +1572,7 @@ export class VisitsManager {
|
||||||
|
|
||||||
// Show confirmation dialog
|
// Show confirmation dialog
|
||||||
const confirmDelete = confirm('Are you sure you want to delete this visit? This action cannot be undone.');
|
const confirmDelete = confirm('Are you sure you want to delete this visit? This action cannot be undone.');
|
||||||
|
|
||||||
if (!confirmDelete) {
|
if (!confirmDelete) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ module LocationSearch
|
||||||
return [] if query.blank?
|
return [] if query.blank?
|
||||||
|
|
||||||
cache_key = "#{@cache_key_prefix}:#{Digest::SHA256.hexdigest(query.downcase)}"
|
cache_key = "#{@cache_key_prefix}:#{Digest::SHA256.hexdigest(query.downcase)}"
|
||||||
|
|
||||||
Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) do
|
Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) do
|
||||||
perform_geocoding_search(query)
|
perform_geocoding_search(query)
|
||||||
end
|
end
|
||||||
|
|
@ -30,33 +30,33 @@ module LocationSearch
|
||||||
|
|
||||||
def perform_geocoding_search(query)
|
def perform_geocoding_search(query)
|
||||||
Rails.logger.info "LocationSearch::GeocodingService: Searching for '#{query}' using #{provider_name}"
|
Rails.logger.info "LocationSearch::GeocodingService: Searching for '#{query}' using #{provider_name}"
|
||||||
|
|
||||||
# Try original query first
|
# Try original query first
|
||||||
results = Geocoder.search(query, limit: MAX_RESULTS)
|
results = Geocoder.search(query, limit: MAX_RESULTS)
|
||||||
Rails.logger.info "LocationSearch::GeocodingService: Raw geocoder returned #{results.length} 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
|
# also try with location context
|
||||||
if results.length > 1 && looks_like_chain_store?(query)
|
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)
|
berlin_results = Geocoder.search("#{query} Berlin", limit: MAX_RESULTS)
|
||||||
Rails.logger.info "LocationSearch::GeocodingService: Berlin-specific search returned #{berlin_results.length} results"
|
Rails.logger.info "LocationSearch::GeocodingService: Berlin-specific search returned #{berlin_results.length} results"
|
||||||
|
|
||||||
# Prioritize Berlin results
|
# Prioritize Berlin results
|
||||||
results = (berlin_results + results).uniq
|
results = (berlin_results + results).uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
return [] if results.blank?
|
return [] if results.blank?
|
||||||
|
|
||||||
normalized = normalize_geocoding_results(results)
|
normalized = normalize_geocoding_results(results)
|
||||||
Rails.logger.info "LocationSearch::GeocodingService: After normalization: #{normalized.length} results"
|
Rails.logger.info "LocationSearch::GeocodingService: After normalization: #{normalized.length} results"
|
||||||
|
|
||||||
normalized
|
normalized
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_geocoding_results(results)
|
def normalize_geocoding_results(results)
|
||||||
normalized_results = []
|
normalized_results = []
|
||||||
|
|
||||||
results.each_with_index do |result, idx|
|
results.each_with_index do |result, idx|
|
||||||
unless valid_result?(result)
|
unless valid_result?(result)
|
||||||
Rails.logger.warn "LocationSearch::GeocodingService: Result #{idx} is invalid: lat=#{result.latitude}, lon=#{result.longitude}"
|
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)
|
# Remove duplicates based on coordinates (within 100m)
|
||||||
deduplicated = deduplicate_results(normalized_results)
|
deduplicated = deduplicate_results(normalized_results)
|
||||||
Rails.logger.info "LocationSearch::GeocodingService: After deduplication: #{deduplicated.length} results"
|
Rails.logger.info "LocationSearch::GeocodingService: After deduplication: #{deduplicated.length} results"
|
||||||
|
|
||||||
deduplicated
|
deduplicated
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid_result?(result)
|
def valid_result?(result)
|
||||||
result.present? &&
|
result.present? &&
|
||||||
result.latitude.present? &&
|
result.latitude.present? &&
|
||||||
result.longitude.present? &&
|
result.longitude.present? &&
|
||||||
result.latitude.to_f.abs <= 90 &&
|
result.latitude.to_f.abs <= 90 &&
|
||||||
result.longitude.to_f.abs <= 180
|
result.longitude.to_f.abs <= 180
|
||||||
|
|
@ -120,7 +120,7 @@ module LocationSearch
|
||||||
|
|
||||||
def extract_type(result)
|
def extract_type(result)
|
||||||
data = result.data || {}
|
data = result.data || {}
|
||||||
|
|
||||||
case provider_name.downcase
|
case provider_name.downcase
|
||||||
when 'photon'
|
when 'photon'
|
||||||
data.dig('properties', 'osm_key') || data.dig('properties', 'type') || 'unknown'
|
data.dig('properties', 'osm_key') || data.dig('properties', 'type') || 'unknown'
|
||||||
|
|
@ -151,13 +151,13 @@ module LocationSearch
|
||||||
def extract_photon_address(result)
|
def extract_photon_address(result)
|
||||||
properties = result.data&.dig('properties') || {}
|
properties = result.data&.dig('properties') || {}
|
||||||
parts = []
|
parts = []
|
||||||
|
|
||||||
parts << properties['street'] if properties['street'].present?
|
parts << properties['street'] if properties['street'].present?
|
||||||
parts << properties['housenumber'] if properties['housenumber'].present?
|
parts << properties['housenumber'] if properties['housenumber'].present?
|
||||||
parts << properties['city'] if properties['city'].present?
|
parts << properties['city'] if properties['city'].present?
|
||||||
parts << properties['state'] if properties['state'].present?
|
parts << properties['state'] if properties['state'].present?
|
||||||
parts << properties['country'] if properties['country'].present?
|
parts << properties['country'] if properties['country'].present?
|
||||||
|
|
||||||
parts.join(', ')
|
parts.join(', ')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -182,7 +182,7 @@ module LocationSearch
|
||||||
|
|
||||||
def deduplicate_results(results)
|
def deduplicate_results(results)
|
||||||
deduplicated = []
|
deduplicated = []
|
||||||
|
|
||||||
results.each do |result|
|
results.each do |result|
|
||||||
# Check if there's already a result within 100m
|
# Check if there's already a result within 100m
|
||||||
duplicate = deduplicated.find do |existing|
|
duplicate = deduplicated.find do |existing|
|
||||||
|
|
@ -192,27 +192,27 @@ module LocationSearch
|
||||||
)
|
)
|
||||||
distance < 100 # meters
|
distance < 100 # meters
|
||||||
end
|
end
|
||||||
|
|
||||||
deduplicated << result unless duplicate
|
deduplicated << result unless duplicate
|
||||||
end
|
end
|
||||||
|
|
||||||
deduplicated
|
deduplicated
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_distance(lat1, lon1, lat2, lon2)
|
def calculate_distance(lat1, lon1, lat2, lon2)
|
||||||
# Haversine formula for distance calculation in meters
|
# Haversine formula for distance calculation in meters
|
||||||
rad_per_deg = Math::PI / 180
|
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
|
dlat_rad = (lat2 - lat1) * rad_per_deg
|
||||||
dlon_rad = (lon2 - lon1) * rad_per_deg
|
dlon_rad = (lon2 - lon1) * rad_per_deg
|
||||||
|
|
||||||
lat1_rad = lat1 * rad_per_deg
|
lat1_rad = lat1 * rad_per_deg
|
||||||
lat2_rad = lat2 * 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
|
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))
|
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||||||
|
|
||||||
rkm * c
|
rkm * c
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -224,8 +224,8 @@ module LocationSearch
|
||||||
/\b(dm|rossmann|müller)\b/i,
|
/\b(dm|rossmann|müller)\b/i,
|
||||||
/\b(h&m|c&a|zara|primark)\b/i
|
/\b(h&m|c&a|zara|primark)\b/i
|
||||||
]
|
]
|
||||||
|
|
||||||
chain_patterns.any? { |pattern| query.match?(pattern) }
|
chain_patterns.any? { |pattern| query.match?(pattern) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,3 @@ MANAGER_URL = SELF_HOSTED ? nil : ENV.fetch('MANAGER_URL', nil)
|
||||||
METRICS_USERNAME = ENV.fetch('METRICS_USERNAME', 'prometheus')
|
METRICS_USERNAME = ENV.fetch('METRICS_USERNAME', 'prometheus')
|
||||||
METRICS_PASSWORD = ENV.fetch('METRICS_PASSWORD', 'prometheus')
|
METRICS_PASSWORD = ENV.fetch('METRICS_PASSWORD', 'prometheus')
|
||||||
# /Prometheus metrics
|
# /Prometheus metrics
|
||||||
|
|
||||||
FEATURES = {
|
|
||||||
location_search: !!ENV.fetch('PHOTON_API_HOST', nil)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
Loading…
Reference in a new issue