Abort search requests when clearing search box

Also adds abort support for Photon queries
This commit is contained in:
2026-03-23 18:07:29 +04:00
parent 8478e00253
commit 86b20fd474
4 changed files with 78 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ import { eq, or } from 'ember-truth-helpers';
export default class SearchBoxComponent extends Component {
@service photon;
@service osm;
@service router;
@service mapUi;
@service map; // Assuming we might need map context, but mostly we use router
@@ -178,6 +179,11 @@ export default class SearchBoxComponent extends Component {
@action
clear() {
this.searchTask.cancelAll();
this.mapUi.stopLoading();
this.osm.cancelAll();
this.photon.cancelAll();
this.query = '';
this.results = [];
if (this.args.onQueryChange) {

View File

@@ -9,6 +9,13 @@ export default class OsmService extends Service {
cachedResults = null;
lastQueryKey = null;
cancelAll() {
if (this.controller) {
this.controller.abort();
this.controller = null;
}
}
async getNearbyPois(lat, lon, radius = 50) {
const queryKey = `${lat},${lon},${radius}`;

View File

@@ -5,6 +5,15 @@ import { humanizeOsmTag } from '../utils/format-text';
export default class PhotonService extends Service {
@service settings;
controller = null;
cancelAll() {
if (this.controller) {
this.controller.abort();
this.controller = null;
}
}
get baseUrl() {
return this.settings.photonApi;
}
@@ -12,6 +21,12 @@ export default class PhotonService extends Service {
async search(query, lat, lon, limit = 10) {
if (!query || query.length < 2) return [];
if (this.controller) {
this.controller.abort();
}
this.controller = new AbortController();
const signal = this.controller.signal;
const params = new URLSearchParams({
q: query,
limit: String(limit),
@@ -25,7 +40,7 @@ export default class PhotonService extends Service {
const url = `${this.baseUrl}?${params.toString()}`;
try {
const res = await this.fetchWithRetry(url);
const res = await this.fetchWithRetry(url, { signal });
if (!res.ok) {
throw new Error(`Photon request failed with status ${res.status}`);
}
@@ -35,6 +50,9 @@ export default class PhotonService extends Service {
return data.features.map((f) => this.normalizeFeature(f));
} catch (e) {
if (e.name === 'AbortError') {
return [];
}
console.error('Photon search error:', e);
// Return empty array on error so UI doesn't break
return [];

View File

@@ -1,10 +1,18 @@
import { module, test } from 'qunit';
import { visit } from '@ember/test-helpers';
import {
visit,
click,
fillIn,
triggerEvent,
currentURL,
} from '@ember/test-helpers';
import { setupApplicationTest } from 'marco/tests/helpers';
import Service from '@ember/service';
import { Promise } from 'rsvp';
class MockPhotonService extends Service {
cancelAll() {}
async search(query) {
// Simulate network delay
await new Promise((resolve) => setTimeout(resolve, 50));
@@ -24,6 +32,8 @@ class MockPhotonService extends Service {
}
class MockOsmService extends Service {
cancelAll() {}
async getCategoryPois(bounds, category) {
await new Promise((resolve) => setTimeout(resolve, 50));
if (category === 'slow_category') {
@@ -94,4 +104,39 @@ module('Acceptance | search loading', function (hooks) {
'Loading state is NOT set for nearby search'
);
});
test('clearing search stops loading indicator', async function (assert) {
const mapUi = this.owner.lookup('service:map-ui');
// 1. Start from index
await visit('/');
// 2. Type "slow" to trigger autocomplete (which is async)
await fillIn('.search-input', 'slow');
// 3. Submit search to trigger route loading
click('.search-submit-btn'); // Intentionally no await to not block on transition
// Wait for loading state to activate
await new Promise((r) => setTimeout(r, 100));
assert.deepEqual(
mapUi.loadingState,
{ type: 'text', value: 'slow' },
'Loading state is set'
);
// 4. Click the clear button (should be visible since input has value)
await click('.search-clear-btn');
// Verify loading state is cleared immediately
assert.strictEqual(
mapUi.loadingState,
null,
'Loading state is cleared immediately after clicking clear'
);
// Verify we are back on index (or at least query is gone)
assert.strictEqual(currentURL(), '/', 'Navigated to index');
});
});