diff --git a/frontend/src/composables/useTaskList.test.ts b/frontend/src/composables/useTaskList.test.ts new file mode 100644 index 000000000..57a2e2b06 --- /dev/null +++ b/frontend/src/composables/useTaskList.test.ts @@ -0,0 +1,83 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest' +import {defineComponent, h, nextTick} from 'vue' +import {mount, flushPromises} from '@vue/test-utils' +import {setActivePinia, createPinia} from 'pinia' +import {createRouter, createMemoryHistory, type Router} from 'vue-router' + +const getAll = vi.fn(async () => []) +vi.mock('@/services/taskCollection', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + default: class { + loading = false + totalPages = 1 + getAll = getAll + }, + } +}) + +import {useTaskList} from './useTaskList' + +// The second positional argument passed to TaskCollectionService.getAll carries +// the sort_by/order_by the backend uses to decide whether to rank by relevance. +function lastRequestParams(): Record { + return getAll.mock.calls.at(-1)?.[1] as Record +} + +async function mountTaskList(query: Record): Promise { + const router = createRouter({ + history: createMemoryHistory(), + routes: [{path: '/', name: 'home', component: {render: () => null}}], + }) + await router.push({path: '/', query}) + await router.isReady() + + const TestComponent = defineComponent({ + setup() { + useTaskList(() => 1, () => 1) + return () => h('div') + }, + }) + + mount(TestComponent, {global: {plugins: [router]}}) + await flushPromises() + await nextTick() + return router +} + +describe('useTaskList sort handling for relevance ranking', () => { + beforeEach(() => { + setActivePinia(createPinia()) + getAll.mockClear() + }) + + it('omits the sort while searching with the default sort so the backend ranks by relevance', async () => { + await mountTaskList({s: 'find me'}) + + const params = lastRequestParams() + expect(params.s).toBe('find me') + expect(params.sort_by).toEqual([]) + expect(params.order_by).toEqual([]) + }) + + it('keeps an explicit user sort while searching so the user sort is respected', async () => { + await mountTaskList({s: 'find me', sort: 'title:asc'}) + + const params = lastRequestParams() + expect(params.s).toBe('find me') + expect(params.sort_by).toEqual(['title']) + expect(params.order_by).toEqual(['asc']) + }) + + it('sends the default sort when not searching', async () => { + await mountTaskList({}) + + const params = lastRequestParams() + expect(params.s).toBe('') + expect(params.sort_by).not.toHaveLength(0) + // id always sorts last so other sort columns take precedence. + expect(params.sort_by).toEqual(['id']) + expect(params.order_by).toEqual(['desc']) + }) +}) diff --git a/frontend/src/composables/useTaskList.ts b/frontend/src/composables/useTaskList.ts index b595e5e09..bae4e7510 100644 --- a/frontend/src/composables/useTaskList.ts +++ b/frontend/src/composables/useTaskList.ts @@ -122,6 +122,14 @@ export function useTaskList( const allParams = computed(() => { const loadParams = {...params.value} + // Relevance ranking only engages when no sort is sent, so omit the default + // sort while searching and let an explicit user sort still take precedence. + if (loadParams.s && !sortQuery.value) { + loadParams.sort_by = [] + loadParams.order_by = [] + return loadParams + } + return formatSortOrder(sortBy.value, loadParams) })