Skip to content

Commit eec6e8a

Browse files
committed
Merge branch 'master' into navi
2 parents 27bfb23 + ffc19f8 commit eec6e8a

5 files changed

Lines changed: 59 additions & 28 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ We have developed an intuitive and simple user interface to find routes:
88

99
[![GraphHopper Maps route planner](https://www.graphhopper.com/wp-content/uploads/2026/03/maps-screenshot.png)](https://graphhopper.com/maps/)
1010

11-
With autocomplete, alternative routes, information along the route and POI search and everything available in all major browsers including mobile browsers. Read more details about it [here](https://www.graphhopper.com/maps-route-planner/).
11+
With address search (autocomplete), alternative routes, information along the route and POI search and everything available in all major browsers including mobile browsers. Read more details about it [here](https://www.graphhopper.com/maps-route-planner/).
1212

1313
## Turn-by-Turn navigation
1414

package-lock.json

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/sidebar/search/AddressInput.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,7 @@ export default function AddressInput(props: AddressInputProps) {
9191
return
9292
}
9393
if (text === '') {
94-
const recents = buildRecentItems(undefined, 5, excludeCoord)
95-
if (recents.length > 0) setAutocompleteItems(recents)
94+
setAutocompleteItems(buildRecentItems(undefined, 5, excludeCoord))
9695
}
9796
}, [hasFocus, excludeCoord])
9897

@@ -230,18 +229,15 @@ export default function AddressInput(props: AddressInputProps) {
230229
setText(query)
231230
if (query === '') {
232231
geocoder.cancel()
233-
const recents = buildRecentItems(undefined, 5, excludeCoord)
234-
if (recents.length > 0) setAutocompleteItems(recents)
235-
else setAutocompleteItems([])
232+
setAutocompleteItems(buildRecentItems(undefined, 5, excludeCoord))
236233
} else {
237234
const coordinate = textToCoordinate(query)
238235
if (coordinate) {
239236
geocoder.cancel()
240237
setAutocompleteItems([])
241238
} else {
242239
if (query.length < 2) {
243-
const recents = buildRecentItems(query, 5, excludeCoord)
244-
if (recents.length > 0) setAutocompleteItems(recents)
240+
setAutocompleteItems(buildRecentItems(query, 5, excludeCoord))
245241
}
246242
geocoder.request(query, biasCoord, getMap().getView().getZoom())
247243
}
@@ -274,9 +270,7 @@ export default function AddressInput(props: AddressInputProps) {
274270
onClick={e => {
275271
setText('')
276272
props.onChange('')
277-
const recents = buildRecentItems(undefined, 5, excludeCoord)
278-
if (recents.length > 0) setAutocompleteItems(recents)
279-
else setAutocompleteItems([])
273+
setAutocompleteItems(buildRecentItems(undefined, 5, excludeCoord))
280274
// if we clear the text without focus then explicitly request it to improve usability:
281275
searchInput.current!.focus()
282276
}}

src/sidebar/search/RecentLocations.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,26 @@ export function saveRecentLocation(
5858
try {
5959
const all = getRecentLocations()
6060
let prevCount = 0
61-
const filtered = all.filter(e => {
61+
const others = all.filter(e => {
6262
const isDuplicate = calcDist({ lat: e.lat, lng: e.lng }, coordinate) <= DEDUP_DISTANCE_METERS
6363
if (isDuplicate) prevCount = e.count
6464
return !isDuplicate
6565
})
6666

67-
filtered.push({
67+
const newEntry: RecentLocation = {
6868
mainText,
6969
secondText,
7070
lat: coordinate.lat,
7171
lng: coordinate.lng,
7272
timestamp: now,
7373
count: prevCount + 1,
74-
})
75-
filtered.sort((a, b) => b.timestamp - a.timestamp)
76-
localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered.slice(0, MAX_ENTRIES)))
74+
}
75+
// Always keep the just-saved entry so a brand-new one-off can enter even if the cache is full of
76+
// favorites (otherwise it would be starved and never accumulate count). Rank the remaining slots
77+
// by count desc then timestamp desc so frequently-used entries survive pressure from one-offs.
78+
others.sort((a, b) => b.count - a.count || b.timestamp - a.timestamp)
79+
const kept = [newEntry, ...others].slice(0, MAX_ENTRIES)
80+
localStorage.setItem(STORAGE_KEY, JSON.stringify(kept))
7781
} catch {
7882
// localStorage unavailable (private browsing, quota exceeded)
7983
}

test/RecentLocations.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,37 @@ describe('RecentLocations', () => {
4141
expect(names()).toContain('P0')
4242
expect(names()).not.toContain('P1') // now the oldest
4343
})
44+
45+
it('should keep a frequently used (high count) location even if older than new one-off entries', () => {
46+
// user visits Home many times long ago
47+
for (let i = 0; i < 5; i++) save('Home', 42, 1000 + i)
48+
// then visits MAX_ENTRIES brand-new one-off locations
49+
for (let i = 0; i < MAX_ENTRIES; i++) save(`One${i}`, 10 + i, 100_000 + i)
50+
51+
// Home has count 5 and should not be evicted by single-count newer entries
52+
expect(names()).toContain('Home')
53+
})
54+
55+
it('should save a new one-off even when the cache is full of frequently-used entries', () => {
56+
// every slot taken by a favorite (count=2)
57+
for (let i = 0; i < MAX_ENTRIES; i++) {
58+
save(`Fav${i}`, 10 + i, 1000 + i)
59+
save(`Fav${i}`, 10 + i, 2000 + i) // re-visit → count=2
60+
}
61+
expect(getRecentLocations()).toHaveLength(MAX_ENTRIES)
62+
63+
save('New', 100, 10_000)
64+
// a brand-new one-off must still be stored, otherwise it can never accumulate count on future visits
65+
expect(names()).toContain('New')
66+
})
67+
68+
it('should sort by count desc, then timestamp desc', () => {
69+
save('Once', 1, 5000)
70+
save('Twice', 2, 1000)
71+
save('Twice', 2, 2000) // count=2
72+
73+
const locs = getRecentLocations()
74+
expect(locs[0].mainText).toBe('Twice') // higher count first
75+
expect(locs[1].mainText).toBe('Once')
76+
})
4477
})

0 commit comments

Comments
 (0)