From c8ba86e8a2ca452580384a3c8e64ec6c7fb7cbf8 Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl <15071461069@163.com> Date: Tue, 17 Mar 2026 11:54:16 +0800 Subject: [PATCH] test: cover skill query helpers --- .../shared/hooks/skill-query-helpers.test.ts | 27 ++++++++++++++ web/src/shared/hooks/skill-query-helpers.ts | 36 +++++++++++++++++++ web/src/shared/hooks/use-skill-queries.ts | 17 ++------- 3 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 web/src/shared/hooks/skill-query-helpers.test.ts create mode 100644 web/src/shared/hooks/skill-query-helpers.ts diff --git a/web/src/shared/hooks/skill-query-helpers.test.ts b/web/src/shared/hooks/skill-query-helpers.test.ts new file mode 100644 index 00000000..33872569 --- /dev/null +++ b/web/src/shared/hooks/skill-query-helpers.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest' +import { buildSkillSearchUrl, shouldEnableNamespaceMemberCandidates } from './skill-query-helpers' + +describe('buildSkillSearchUrl', () => { + it('normalizes the query and strips the namespace prefix', () => { + expect(buildSkillSearchUrl({ + q: ' hello world ', + namespace: '@team-ai', + sort: 'relevance', + page: 2, + size: 12, + })).toBe('/api/web/skills?q=hello+world&namespace=team-ai&sort=relevance&page=2&size=12') + }) + + it('returns the base skills endpoint when no search params are provided', () => { + expect(buildSkillSearchUrl({})).toBe('/api/web/skills') + }) +}) + +describe('shouldEnableNamespaceMemberCandidates', () => { + it('enables the query only when slug exists and search text has at least two non-space characters', () => { + expect(shouldEnableNamespaceMemberCandidates('team-ai', 'ab')).toBe(true) + expect(shouldEnableNamespaceMemberCandidates('team-ai', ' a ')).toBe(false) + expect(shouldEnableNamespaceMemberCandidates('', 'admin')).toBe(false) + expect(shouldEnableNamespaceMemberCandidates('team-ai', 'admin', false)).toBe(false) + }) +}) diff --git a/web/src/shared/hooks/skill-query-helpers.ts b/web/src/shared/hooks/skill-query-helpers.ts new file mode 100644 index 00000000..dcc6e0b2 --- /dev/null +++ b/web/src/shared/hooks/skill-query-helpers.ts @@ -0,0 +1,36 @@ +import type { SearchParams } from '@/api/types' +import { WEB_API_PREFIX } from '@/api/client' +import { normalizeSearchQuery } from '@/shared/lib/search-query' + +export function buildSkillSearchUrl(params: SearchParams) { + const queryParams = new URLSearchParams() + const normalizedQuery = normalizeSearchQuery(params.q ?? '') + + if (normalizedQuery) { + queryParams.append('q', normalizedQuery) + } + + if (params.namespace) { + const cleanNamespace = params.namespace.startsWith('@') ? params.namespace.slice(1) : params.namespace + queryParams.append('namespace', cleanNamespace) + } + + if (params.sort) { + queryParams.append('sort', params.sort) + } + + if (params.page !== undefined) { + queryParams.append('page', String(params.page)) + } + + if (params.size !== undefined) { + queryParams.append('size', String(params.size)) + } + + const queryString = queryParams.toString() + return queryString ? `${WEB_API_PREFIX}/skills?${queryString}` : `${WEB_API_PREFIX}/skills` +} + +export function shouldEnableNamespaceMemberCandidates(slug: string, search: string, enabled = true) { + return enabled && !!slug && search.trim().length >= 2 +} diff --git a/web/src/shared/hooks/use-skill-queries.ts b/web/src/shared/hooks/use-skill-queries.ts index 11d238c7..f9b9b24d 100644 --- a/web/src/shared/hooks/use-skill-queries.ts +++ b/web/src/shared/hooks/use-skill-queries.ts @@ -2,23 +2,12 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import type { SkillSummary, SkillDetail, SkillVersion, SkillVersionDetail, SkillFile, SearchParams, PagedResponse, PublishResult, Namespace, NamespaceMember, ManagedNamespace, CreateNamespaceRequest, NamespaceCandidateUser, NamespaceRole } from '@/api/types' import { fetchJson, fetchText, getCsrfHeaders, meApi, namespaceApi, promotionApi, skillLifecycleApi, WEB_API_PREFIX } from '@/api/client' import { appendNamespaceMember, replaceNamespaceMemberRole } from '@/shared/lib/namespace-member-cache' -import { normalizeSearchQuery } from '@/shared/lib/search-query' +import { buildSkillSearchUrl, shouldEnableNamespaceMemberCandidates } from './skill-query-helpers' const PUBLISH_REQUEST_TIMEOUT_MS = 60_000 async function searchSkills(params: SearchParams): Promise> { - const queryParams = new URLSearchParams() - const normalizedQuery = normalizeSearchQuery(params.q ?? '') - if (normalizedQuery) queryParams.append('q', normalizedQuery) - if (params.namespace) { - const cleanNamespace = params.namespace.startsWith('@') ? params.namespace.slice(1) : params.namespace - queryParams.append('namespace', cleanNamespace) - } - if (params.sort) queryParams.append('sort', params.sort) - if (params.page !== undefined) queryParams.append('page', String(params.page)) - if (params.size !== undefined) queryParams.append('size', String(params.size)) - - return fetchJson>(`${WEB_API_PREFIX}/skills?${queryParams.toString()}`) + return fetchJson>(buildSkillSearchUrl(params)) } async function getSkillDetail(namespace: string, slug: string): Promise { @@ -226,7 +215,7 @@ export function useNamespaceMemberCandidates(slug: string, search: string, enabl return useQuery({ queryKey: ['namespaces', slug, 'member-candidates', search], queryFn: () => searchNamespaceMemberCandidates({ slug, search }), - enabled: enabled && !!slug && search.trim().length >= 2, + enabled: shouldEnableNamespaceMemberCandidates(slug, search, enabled), }) }