import { mount } from '@vue/test-utils' import { describe, expect, it, vi } from 'vitest' import { defineComponent, nextTick, ref } from 'vue' import { reorderList, useListReorder, useReorderAnnouncer, type ListReorderMove, } from '../../composables/useListReorder' interface Row { id: string; name: string } function mountHarness(onReorder: (move: ListReorderMove) => void, disabled = false) { const items = ref([ { id: 'a', name: 'Alpha' }, { id: 'b', name: 'Beta' }, { id: 'c', name: 'Gamma' }, ]) const wrapper = mount(defineComponent({ setup() { const reorder = useListReorder({ items: () => items.value, getId: (row) => row.id, getLabel: (row) => row.name, disabled: () => disabled, itemNoun: 'Row', onReorder, }) return { items, ...reorder } }, template: `
  • {{ row.name }}

{{ announcement }}

`, }), { attachTo: document.body }) return { wrapper, items } } describe('reorderList', () => { it('returns a copy with the item moved, leaving the source untouched', () => { const source = ['a', 'b', 'c'] expect(reorderList(source, 0, 2)).toEqual(['b', 'c', 'a']) expect(source).toEqual(['a', 'b', 'c']) }) }) describe('useReorderAnnouncer', () => { it('publishes messages and re-announces an identical message', async () => { const { announcement, announce } = useReorderAnnouncer() announce('Alpha moved.') await nextTick() expect(announcement.value).toBe('Alpha moved.') announce('Alpha moved.') expect(announcement.value).toBe('') await nextTick() expect(announcement.value).toBe('Alpha moved.') }) }) describe('useListReorder', () => { it('moves an item one position later on Alt+ArrowDown and announces it', async () => { const onReorder = vi.fn() const { wrapper } = mountHarness(onReorder) await wrapper.get('[data-id="a"]').trigger('keydown', { key: 'ArrowDown', altKey: true }) expect(onReorder).toHaveBeenCalledTimes(1) const move = onReorder.mock.calls[0][0] as ListReorderMove expect(move.fromIndex).toBe(0) expect(move.toIndex).toBe(1) expect(move.source).toBe('keyboard') expect(move.items.map(row => row.id)).toEqual(['b', 'a', 'c']) await nextTick() expect(wrapper.get('.announcement').text()).toBe('Alpha moved to position 2 of 3.') wrapper.unmount() }) it('moves an item one position earlier on Alt+ArrowUp and Alt+ArrowLeft', async () => { const onReorder = vi.fn() const { wrapper } = mountHarness(onReorder) await wrapper.get('[data-id="c"]').trigger('keydown', { key: 'ArrowUp', altKey: true }) await wrapper.get('[data-id="c"]').trigger('keydown', { key: 'ArrowLeft', altKey: true }) expect(onReorder.mock.calls.map(call => (call[0] as ListReorderMove).toIndex)).toEqual([1, 1]) wrapper.unmount() }) it('ignores arrow keys without Alt and moves past either end', async () => { const onReorder = vi.fn() const { wrapper } = mountHarness(onReorder) await wrapper.get('[data-id="a"]').trigger('keydown', { key: 'ArrowDown' }) await wrapper.get('[data-id="a"]').trigger('keydown', { key: 'ArrowUp', altKey: true }) await wrapper.get('[data-id="c"]').trigger('keydown', { key: 'ArrowDown', altKey: true }) expect(onReorder).not.toHaveBeenCalled() wrapper.unmount() }) it('reorders through a drag and clears its drag state', async () => { const onReorder = vi.fn() const { wrapper } = mountHarness(onReorder) const dataTransfer = { effectAllowed: '', dropEffect: '', setData: vi.fn() } await wrapper.get('[data-id="a"]').trigger('dragstart', { dataTransfer }) await wrapper.get('[data-id="c"]').trigger('dragover', { dataTransfer }) await wrapper.get('[data-id="c"]').trigger('drop', { dataTransfer }) const move = onReorder.mock.calls[0][0] as ListReorderMove expect(move).toMatchObject({ fromIndex: 0, toIndex: 2, source: 'drag' }) expect(move.items.map(row => row.id)).toEqual(['b', 'c', 'a']) expect(dataTransfer.setData).toHaveBeenCalledWith('text/plain', 'a') wrapper.unmount() }) it('blocks every reorder while disabled', async () => { const onReorder = vi.fn() const { wrapper } = mountHarness(onReorder, true) await wrapper.get('[data-id="a"]').trigger('keydown', { key: 'ArrowDown', altKey: true }) await wrapper.get('[data-id="a"]').trigger('dragstart', { dataTransfer: { setData: vi.fn() } }) await wrapper.get('[data-id="b"]').trigger('drop', { dataTransfer: { setData: vi.fn() } }) expect(onReorder).not.toHaveBeenCalled() wrapper.unmount() }) })