fix: letter sorting in image dropdown (#8277)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8277-fix-letter-sorting-in-image-dropdown-2f16d73d3650818ea82dff944d345ec7)
by [Unito](https://www.unito.io)
This commit is contained in:
Rizumu Ayaka
2026-01-24 09:30:20 +07:00
committed by GitHub
parent 7c61dadaf2
commit 3e9a390c25
2 changed files with 157 additions and 1 deletions

View File

@@ -0,0 +1,151 @@
import { describe, expect, it } from 'vitest'
import { defaultSearcher, getDefaultSortOptions } from './shared'
import type { DropdownItem } from './types'
function createItem(name: string, label?: string): DropdownItem {
return {
id: name,
mediaSrc: '',
name,
label,
metadata: ''
}
}
describe('defaultSearcher', () => {
const items: DropdownItem[] = [
createItem('apple.png'),
createItem('banana.jpg'),
createItem('cherry.gif')
]
it('returns all items when query is empty', async () => {
const result = await defaultSearcher('', items)
expect(result).toEqual(items)
})
it('returns all items when query is whitespace', async () => {
const result = await defaultSearcher(' ', items)
expect(result).toEqual(items)
})
it('filters items by single word', async () => {
const result = await defaultSearcher('apple', items)
expect(result).toHaveLength(1)
expect(result[0].name).toBe('apple.png')
})
it('filters items case-insensitively', async () => {
const result = await defaultSearcher('APPLE', items)
expect(result).toHaveLength(1)
expect(result[0].name).toBe('apple.png')
})
it('filters items by multiple words (AND logic)', async () => {
const result = await defaultSearcher('a png', items)
expect(result).toHaveLength(1)
expect(result[0].name).toBe('apple.png')
})
it('returns empty array when no matches', async () => {
const result = await defaultSearcher('xyz', items)
expect(result).toHaveLength(0)
})
})
describe('getDefaultSortOptions', () => {
const sortOptions = getDefaultSortOptions()
describe('Default sorter', () => {
const defaultSorter = sortOptions.find((o) => o.id === 'default')!.sorter
it('returns items in original order', () => {
const items = [createItem('z'), createItem('a'), createItem('m')]
const result = defaultSorter({ items })
expect(result.map((i) => i.name)).toEqual(['z', 'a', 'm'])
})
it('does not mutate original array', () => {
const items = [createItem('z'), createItem('a')]
const result = defaultSorter({ items })
expect(result).not.toBe(items)
})
})
describe('A-Z sorter', () => {
const azSorter = sortOptions.find((o) => o.id === 'a-z')!.sorter
it('sorts items alphabetically by name', () => {
const items = [
createItem('cherry'),
createItem('apple'),
createItem('banana')
]
const result = azSorter({ items })
expect(result.map((i) => i.name)).toEqual(['apple', 'banana', 'cherry'])
})
it('sorts items alphabetically by label when available', () => {
const items = [
createItem('file_c.png', 'Cherry'),
createItem('file_a.png', 'Apple'),
createItem('file_b.png', 'Banana')
]
const result = azSorter({ items })
expect(result.map((i) => i.name)).toEqual([
'file_a.png',
'file_b.png',
'file_c.png'
])
})
it('uses natural sort for numeric values', () => {
const items = [
createItem('img_10.png'),
createItem('img_2.png'),
createItem('img_1.png'),
createItem('img_20.png')
]
const result = azSorter({ items })
expect(result.map((i) => i.name)).toEqual([
'img_1.png',
'img_2.png',
'img_10.png',
'img_20.png'
])
})
it('is case-insensitive', () => {
const items = [
createItem('Banana'),
createItem('apple'),
createItem('CHERRY')
]
const result = azSorter({ items })
expect(result.map((i) => i.name)).toEqual(['apple', 'Banana', 'CHERRY'])
})
it('falls back to name when label is undefined', () => {
const items = [
createItem('z_file.png', 'Alpha'),
createItem('a_file.png'),
createItem('m_file.png', 'Beta')
]
const result = azSorter({ items })
// 'a_file.png' (no label, uses name), 'Alpha', 'Beta'
expect(result.map((i) => i.name)).toEqual([
'a_file.png',
'z_file.png',
'm_file.png'
])
})
it('does not mutate original array', () => {
const items = [createItem('z'), createItem('a')]
const result = azSorter({ items })
expect(result).not.toBe(items)
expect(items[0].name).toBe('z')
})
})
})

View File

@@ -21,7 +21,12 @@ export function getDefaultSortOptions(): SortOption[] {
id: 'a-z',
sorter: ({ items }) =>
items.slice().sort((a, b) => {
return a.name.localeCompare(b.name)
const aLabel = a.label ?? a.name
const bLabel = b.label ?? b.name
return aLabel.localeCompare(bLabel, undefined, {
numeric: true,
sensitivity: 'base'
})
})
}
]