diff --git a/app/routes/place.js b/app/routes/place.js index 774574f..451f58c 100644 --- a/app/routes/place.js +++ b/app/routes/place.js @@ -56,6 +56,8 @@ export default class PlaceRoute extends Route { deactivate() { // Clear the pin when leaving the route this.mapUi.clearSelection(); + // Reset the "return to search" flag so it doesn't persist to subsequent navigations + this.mapUi.returnToSearch = false; } async loadOsmPlace(id, type = null) { diff --git a/app/services/map-ui.js b/app/services/map-ui.js index c125cbd..abfa140 100644 --- a/app/services/map-ui.js +++ b/app/services/map-ui.js @@ -6,6 +6,7 @@ export default class MapUiService extends Service { @tracked isSearching = false; @tracked isCreating = false; @tracked creationCoordinates = null; + @tracked returnToSearch = false; selectPlace(place) { this.selectedPlace = place; diff --git a/app/templates/place.gjs b/app/templates/place.gjs index a4c869e..155a3f1 100644 --- a/app/templates/place.gjs +++ b/app/templates/place.gjs @@ -77,11 +77,11 @@ export default class PlaceTemplate extends Component { navigateBack(place) { // The sidebar calls this with null when "Back" is clicked. if (place === null) { - // If we have history, go back (preserves search state) - if (window.history.length > 1) { + // If we came from search results, go back in history + if (this.mapUi.returnToSearch) { window.history.back(); } else { - // Fallback if opened directly + // Otherwise just close the sidebar (return to map index) this.router.transitionTo('index'); } } else { diff --git a/app/templates/search.gjs b/app/templates/search.gjs index ed2f59b..5412155 100644 --- a/app/templates/search.gjs +++ b/app/templates/search.gjs @@ -5,10 +5,12 @@ import { action } from '@ember/object'; export default class SearchTemplate extends Component { @service router; + @service mapUi; @action selectPlace(place) { if (place) { + this.mapUi.returnToSearch = true; this.router.transitionTo('place', place); } } diff --git a/tests/acceptance/navigation-test.js b/tests/acceptance/navigation-test.js new file mode 100644 index 0000000..fbfa48e --- /dev/null +++ b/tests/acceptance/navigation-test.js @@ -0,0 +1,106 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, click, settled } from '@ember/test-helpers'; +import { setupApplicationTest } from 'marco/tests/helpers'; +import Service from '@ember/service'; +import sinon from 'sinon'; + +class MockOsmService extends Service { + async getNearbyPois() { + return [ + { + osmId: '123', + lat: 1, + lon: 1, + osmTags: { name: 'Test Place', amenity: 'cafe' }, + osmType: 'node', + }, + ]; + } + async getPoiById() { + return { + osmId: '123', + lat: 1, + lon: 1, + osmTags: { name: 'Test Place', amenity: 'cafe' }, + osmType: 'node', + }; + } +} + +class MockStorageService extends Service { + savedPlaces = []; + findPlaceById() { + return null; + } + loadPlacesInBounds() { + return []; + } + get placesInView() { + return []; + } + rs = { + on: () => {}, + }; +} + +module('Acceptance | navigation', function (hooks) { + setupApplicationTest(hooks); + + hooks.beforeEach(function () { + this.owner.register('service:osm', MockOsmService); + this.owner.register('service:storage', MockStorageService); + }); + + test('navigating from search results to place and back uses history', async function (assert) { + const mapUi = this.owner.lookup('service:map-ui'); + const backStub = sinon.stub(window.history, 'back'); + + try { + await visit('/search?lat=1&lon=1'); + assert.strictEqual(currentURL(), '/search?lat=1&lon=1'); + + await click('.place-item'); + assert.ok(currentURL().includes('/place/'), 'Navigated to place'); + assert.true(mapUi.returnToSearch, 'Flag returnToSearch is set'); + + // Click the back button in the sidebar + await click('.back-btn'); + + assert.true(backStub.calledOnce, 'window.history.back() was called'); + } finally { + backStub.restore(); + } + }); + + test('closing the sidebar resets the returnToSearch flag', async function (assert) { + const mapUi = this.owner.lookup('service:map-ui'); + + await visit('/search?lat=1&lon=1'); + await click('.place-item'); // Sets returnToSearch = true + + assert.true(mapUi.returnToSearch, 'Flag is set upon entering place'); + + // Click the Close (X) button + await click('.close-btn'); + await settled(); + + assert.strictEqual(currentURL(), '/', 'Returned to index'); + assert.false(mapUi.returnToSearch, 'Flag is reset after closing sidebar'); + }); + + test('navigating directly to place and back closes sidebar', async function (assert) { + const backStub = sinon.stub(window.history, 'back'); + try { + await visit('/place/osm:node:123'); + assert.ok(currentURL().includes('/place/'), 'Visited place directly'); + + await click('.back-btn'); + await settled(); + + assert.strictEqual(currentURL(), '/', 'Returned to index/map'); + assert.true(backStub.notCalled, 'window.history.back() was NOT called'); + } finally { + backStub.restore(); + } + }); +});