diff --git a/shell/plugins/menu/MenuModel.js b/shell/plugins/menu/MenuModel.js index 12ef3c0c..3de9b8ee 100644 --- a/shell/plugins/menu/MenuModel.js +++ b/shell/plugins/menu/MenuModel.js @@ -309,6 +309,12 @@ function searchScore(items, entry, query) { else if (descriptionTextMatches(needle, descriptionText)) score = 60 if (entry.kind === "menu" || entry.kind === "link") score -= 2 + // mergeAppRows appends app rows after every menu item, so an app always + // loses the order tiebreak below to a menu entry that matches just as well. + // Searching an installed app's name should launch it, not offer to install + // or remove it. The bias stays under the 10-point gap between match tiers, + // so a menu entry that matches better still sorts first. + if (entry.kind === "app") score -= 5 return score * 1000 + depthFor(items, entry.id) * 25 + entry.order } diff --git a/test/shell.d/menu-test.sh b/test/shell.d/menu-test.sh index 52cedb92..8f838702 100644 --- a/test/shell.d/menu-test.sh +++ b/test/shell.d/menu-test.sh @@ -109,6 +109,26 @@ assertDeepEqual( const defaultItems = menu.parseMenuJsonc(defaultMenuJsonc) const defaultById = Object.fromEntries(defaultItems.map(item => [item.id, item])) + +// App rows land after every static item, so ranking has to survive the real +// menu's item count: with hundreds of entries ahead of them, the order +// tiebreak alone buries an installed app under Install and Remove. +const rankBase = menu.mergeMenuSources(defaultItems, []) +const ranked = menu.mergeAppRows(rankBase.items, rankBase.itemOrder, [ + { id: 'apps.brave', parent: 'apps', kind: 'app', label: 'Brave', description: '', aliases: [] }, + { id: 'apps.fontforge', parent: 'apps', kind: 'app', label: 'FontForge', description: '', aliases: [] } +]) +const rankScore = (id, query) => menu.searchScore(ranked.items, ranked.items[id], query) +assert( + ['install.browser.brave', 'remove.browser.brave', 'setup.default.browser.brave'].every( + id => rankScore('apps.brave', 'brave') < rankScore(id, 'brave') + ), + 'menu ranks an installed app above menu entries matching the query equally well' +) +assert( + rankScore('style.font', 'font') < rankScore('apps.fontforge', 'font'), + 'menu keeps a better-matching menu entry above a weaker app match' +) const triggerItems = defaultItems.filter(item => item.parent === 'trigger') assertEqual( triggerItems[0].id,