diff --git a/app/components/SearchSuggestionCard.vue b/app/components/SearchSuggestionCard.vue index 26e29f745..c50ad8ed7 100644 --- a/app/components/SearchSuggestionCard.vue +++ b/app/components/SearchSuggestionCard.vue @@ -16,8 +16,8 @@ defineProps<{ { existenceCache.value = { ...existenceCache.value, [`org:${lowerName}`]: exists } }) @@ -390,7 +390,7 @@ export function useSearch( if (wantUser && existenceCache.value[`user:${lowerName}`] === undefined) { promises.push( - checkUserNpm(name) + checkUserNpm(lowerName) .then(exists => { existenceCache.value = { ...existenceCache.value, [`user:${lowerName}`]: exists } }) @@ -411,10 +411,10 @@ export function useSearch( const isUser = wantUser && existenceCache.value[`user:${lowerName}`] if (isOrg) { - result.push({ type: 'org', name, exists: true }) + result.push({ type: 'org', name: lowerName, exists: true }) } if (isUser && !isOrg) { - result.push({ type: 'user', name, exists: true }) + result.push({ type: 'user', name: lowerName, exists: true }) } } finally { if (requestId === suggestionRequestId.value) { diff --git a/app/pages/org/[org].vue b/app/pages/org/[org].vue index 3291640cd..e7ec4a649 100644 --- a/app/pages/org/[org].vue +++ b/app/pages/org/[org].vue @@ -10,7 +10,7 @@ definePageMeta({ const route = useRoute('org') const router = useRouter() -const orgName = computed(() => route.params.org) +const orgName = computed(() => route.params.org.toLowerCase()) const { isConnected } = useConnector() diff --git a/app/pages/~[username]/index.vue b/app/pages/~[username]/index.vue index 5634925d9..cb2394e70 100644 --- a/app/pages/~[username]/index.vue +++ b/app/pages/~[username]/index.vue @@ -5,7 +5,7 @@ import { normalizeSearchParam } from '#shared/utils/url' const route = useRoute('~username') const router = useRouter() -const username = computed(() => route.params.username) +const username = computed(() => route.params.username.toLowerCase()) // Debounced URL update for page and filter/sort const updateUrl = debounce((updates: { page?: number; filter?: string; sort?: string }) => { diff --git a/server/api/registry/org/[org]/packages.get.ts b/server/api/registry/org/[org]/packages.get.ts index 4dd2019b5..c9b621015 100644 --- a/server/api/registry/org/[org]/packages.get.ts +++ b/server/api/registry/org/[org]/packages.get.ts @@ -16,7 +16,7 @@ function validateOrgName(name: string): void { export default defineCachedEventHandler( async event => { - const org = getRouterParam(event, 'org') + const org = getRouterParam(event, 'org')?.toLowerCase() if (!org) { throw createError({ @@ -54,7 +54,7 @@ export default defineCachedEventHandler( maxAge: CACHE_MAX_AGE_ONE_HOUR, swr: true, getKey: event => { - const org = getRouterParam(event, 'org') ?? '' + const org = getRouterParam(event, 'org')?.toLowerCase() ?? '' return `org-packages:v1:${org}` }, }, diff --git a/test/unit/search-case-normalization.spec.ts b/test/unit/search-case-normalization.spec.ts new file mode 100644 index 000000000..b13227338 --- /dev/null +++ b/test/unit/search-case-normalization.spec.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from 'vitest' +import { parseSuggestionIntent } from '../../app/composables/npm/search-utils' + +describe('Search case normalization', () => { + describe('parseSuggestionIntent', () => { + it('should preserve case in name extraction for org queries', () => { + const result = parseSuggestionIntent('@AnGuLAR') + expect(result.intent).toBe('org') + expect(result.name).toBe('AnGuLAR') + }) + + it('should preserve case in name extraction for user queries', () => { + const result = parseSuggestionIntent('~daNIEL') + expect(result.intent).toBe('user') + expect(result.name).toBe('daNIEL') + }) + + it('should preserve case in name extraction for both queries', () => { + const result = parseSuggestionIntent('AnGuLAR') + expect(result.intent).toBe('both') + expect(result.name).toBe('AnGuLAR') + }) + + it('should handle lowercase org names', () => { + const result = parseSuggestionIntent('@angular') + expect(result.intent).toBe('org') + expect(result.name).toBe('angular') + }) + + it('should handle lowercase user names', () => { + const result = parseSuggestionIntent('~daniel') + expect(result.intent).toBe('user') + expect(result.name).toBe('daniel') + }) + }) + + describe('Case normalization expectations', () => { + it('should expect suggestions to normalize names to lowercase', () => { + const mixedCaseOrg = parseSuggestionIntent('@AnGuLAR') + expect(mixedCaseOrg.name).toBe('AnGuLAR') + + const expectedNormalized = mixedCaseOrg.name.toLowerCase() + expect(expectedNormalized).toBe('angular') + }) + }) +})