[bugfix] Fix shift+click deselection in asset panel (#8396)

## Summary
Fix shift+click range selection not properly deselecting assets when
selecting a smaller range, and improve selection performance.

## Changes
- **Bug Fix**: Shift+click now replaces selection with the new range
instead of combining with existing selection
- **Performance**: Remove unnecessary `.every()` check in `setSelection`
(O(n) → O(1))
- **Tests**: Add 23 unit tests for asset selection logic

## Test Plan
- [x] Click 1st asset → only 1st selected
- [x] Shift+click 3rd asset → items 1-3 selected
- [x] Shift+click 1st asset → only 1st selected (was broken, now fixed)
- [x] All 23 new unit tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8396-bugfix-Fix-shift-click-deselection-in-asset-panel-2f76d73d3650814ca060d1e6a40cf6d4)
by [Unito](https://www.unito.io)
This commit is contained in:
Jin Yi
2026-01-30 09:21:08 +09:00
committed by GitHub
parent f1cf8073d6
commit d437b96238
4 changed files with 342 additions and 14 deletions

View File

@@ -0,0 +1,138 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it } from 'vitest'
import { useAssetSelectionStore } from './useAssetSelectionStore'
describe('useAssetSelectionStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
describe('addToSelection', () => {
it('adds an asset ID to the selection', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
expect(store.isSelected('asset-1')).toBe(true)
expect(store.selectedCount).toBe(1)
})
it('can add multiple IDs', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
store.addToSelection('asset-2')
expect(store.selectedCount).toBe(2)
})
})
describe('removeFromSelection', () => {
it('removes an asset ID from the selection', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
store.removeFromSelection('asset-1')
expect(store.isSelected('asset-1')).toBe(false)
expect(store.selectedCount).toBe(0)
})
})
describe('setSelection', () => {
it('replaces entire selection with new IDs', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
store.addToSelection('asset-2')
store.setSelection(['asset-3', 'asset-4'])
expect(store.isSelected('asset-1')).toBe(false)
expect(store.isSelected('asset-2')).toBe(false)
expect(store.isSelected('asset-3')).toBe(true)
expect(store.isSelected('asset-4')).toBe(true)
expect(store.selectedCount).toBe(2)
})
it('can set to empty selection', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
store.setSelection([])
expect(store.selectedCount).toBe(0)
})
})
describe('clearSelection', () => {
it('clears all selections and resets lastSelectedIndex', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
store.setLastSelectedIndex(5)
store.clearSelection()
expect(store.selectedCount).toBe(0)
expect(store.lastSelectedIndex).toBe(-1)
})
})
describe('toggleSelection', () => {
it('adds unselected item', () => {
const store = useAssetSelectionStore()
store.toggleSelection('asset-1')
expect(store.isSelected('asset-1')).toBe(true)
})
it('removes selected item', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
store.toggleSelection('asset-1')
expect(store.isSelected('asset-1')).toBe(false)
})
})
describe('isSelected', () => {
it('returns true for selected items', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
expect(store.isSelected('asset-1')).toBe(true)
})
it('returns false for unselected items', () => {
const store = useAssetSelectionStore()
expect(store.isSelected('asset-1')).toBe(false)
})
})
describe('setLastSelectedIndex', () => {
it('updates lastSelectedIndex', () => {
const store = useAssetSelectionStore()
store.setLastSelectedIndex(10)
expect(store.lastSelectedIndex).toBe(10)
})
})
describe('reset', () => {
it('clears selection and resets index', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
store.setLastSelectedIndex(5)
store.reset()
expect(store.selectedCount).toBe(0)
expect(store.lastSelectedIndex).toBe(-1)
})
})
describe('computed properties', () => {
it('hasSelection returns true when items are selected', () => {
const store = useAssetSelectionStore()
expect(store.hasSelection).toBe(false)
store.addToSelection('asset-1')
expect(store.hasSelection).toBe(true)
})
it('selectedIdsArray returns array of selected IDs', () => {
const store = useAssetSelectionStore()
store.addToSelection('asset-1')
store.addToSelection('asset-2')
expect(store.selectedIdsArray).toContain('asset-1')
expect(store.selectedIdsArray).toContain('asset-2')
})
})
})