From 0f29430e1ab7184c758aa313c3cf716353ec42f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Fri, 27 Mar 2026 15:01:04 +0400 Subject: [PATCH] When request retries exhaust, show error in toast notification --- app/routes/search.js | 9 ++++-- app/services/osm.js | 2 +- tests/acceptance/search-test.js | 52 +++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/app/routes/search.js b/app/routes/search.js index cf876a4..e47eb08 100644 --- a/app/routes/search.js +++ b/app/routes/search.js @@ -9,6 +9,7 @@ export default class SearchRoute extends Route { @service mapUi; @service storage; @service router; + @service toast; queryParams = { lat: { refreshModel: true }, @@ -199,8 +200,12 @@ export default class SearchRoute extends Route { } @action - error() { + error(error, transition) { this.mapUi.stopSearch(); - return true; // Bubble error + this.toast.show('Search request failed. Please try again.'); + if (transition) { + transition.abort(); + } + return false; // Prevent bubble and stop transition } } diff --git a/app/services/osm.js b/app/services/osm.js index 9132a2c..e265178 100644 --- a/app/services/osm.js +++ b/app/services/osm.js @@ -155,7 +155,7 @@ out center; return []; } console.error('Category search failed', e); - return []; + throw e; } } diff --git a/tests/acceptance/search-test.js b/tests/acceptance/search-test.js index c344631..59b97c0 100644 --- a/tests/acceptance/search-test.js +++ b/tests/acceptance/search-test.js @@ -218,4 +218,56 @@ module('Acceptance | search', function (hooks) { // Ensure it shows "Results" not "Nearby" assert.dom('.sidebar-header h2').includesText('Results'); }); + + test('search error handling prevents opening empty panel and shows toast', async function (assert) { + // Mock Osm Service to throw an error + class MockOsmService extends Service { + async getCategoryPois() { + throw new Error('Overpass request failed'); + } + } + this.owner.register('service:osm', MockOsmService); + + class MockStorageService extends Service { + savedPlaces = []; + findPlaceById() { + return null; + } + isPlaceSaved() { + return false; + } + rs = { on: () => {} }; + placesInView = []; + loadPlacesInBounds() { + return Promise.resolve(); + } + } + this.owner.register('service:storage', MockStorageService); + + class MockMapService extends Service { + getBounds() { + return { + minLat: 52.5, + minLon: 13.4, + maxLat: 52.6, + maxLon: 13.5, + }; + } + } + this.owner.register('service:map', MockMapService); + + await visit('/'); + + try { + await visit('/search?category=coffee&lat=52.52&lon=13.405'); + } catch (e) { + // Aborted transition throws, which is expected + } + + assert.dom('.toast-notification').exists('Toast should be visible'); + assert + .dom('.toast-notification') + .hasText('Search request failed. Please try again.'); + assert.dom('.places-sidebar').doesNotExist('Results panel should not open'); + }); });