Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/components/SearchSuggestionCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ defineProps<{
<NuxtLink
:to="
type === 'user'
? { name: '~username', params: { username: name } }
: { name: 'org', params: { org: name } }
? { name: '~username', params: { username: name.toLowerCase() } }
: { name: 'org', params: { org: name.toLowerCase() } }
"
:data-suggestion-index="index"
class="flex items-center gap-4 focus-visible:outline-none after:content-[''] after:absolute after:inset-0"
Expand Down
12 changes: 6 additions & 6 deletions app/composables/npm/useSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ export function useSearch(

const newSuggestions: SearchSuggestion[] = []
if (isOrg) {
newSuggestions.push({ type: 'org', name, exists: true })
newSuggestions.push({ type: 'org', name: lowerName, exists: true })
}
if (isUser && !isOrg) {
newSuggestions.push({ type: 'user', name, exists: true })
newSuggestions.push({ type: 'user', name: lowerName, exists: true })
}
suggestions.value = newSuggestions
} else {
Expand Down Expand Up @@ -378,7 +378,7 @@ export function useSearch(

if (wantOrg && existenceCache.value[`org:${lowerName}`] === undefined) {
promises.push(
checkOrgNpm(name)
checkOrgNpm(lowerName)
.then(exists => {
existenceCache.value = { ...existenceCache.value, [`org:${lowerName}`]: exists }
})
Expand All @@ -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 }
})
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion app/pages/org/[org].vue
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion app/pages/~[username]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down
4 changes: 2 additions & 2 deletions server/api/registry/org/[org]/packages.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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}`
},
},
Expand Down
46 changes: 46 additions & 0 deletions test/unit/search-case-normalization.spec.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})
})
Loading