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
1 change: 1 addition & 0 deletions components/transfer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@dhis2-ui/loader": "10.17.0",
"@dhis2-ui/tooltip": "10.17.0",
"@dhis2/ui-constants": "10.17.0",
"@dnd-kit/core": "^6.3.1",
"classnames": "^2.3.1",
"prop-types": "^15.7.2"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { addSourceOptionsOnDrop } from '../../transfer/add-source-options-on-drop.js'

describe('Transfer - addSourceOptionsOnDrop', () => {
const onChange = jest.fn()
const setHighlightedSourceOptions = jest.fn()

afterEach(() => {
onChange.mockClear()
setHighlightedSourceOptions.mockClear()
})

it('should append when no drop index is provided', () => {
addSourceOptionsOnDrop({
selected: ['a', 'b'],
draggedValues: ['x'],
maxSelections: Infinity,
onChange,
setHighlightedSourceOptions,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['a', 'b', 'x'],
})
})

it('should insert at the provided drop index', () => {
addSourceOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['x'],
dropIndex: 1,
maxSelections: Infinity,
onChange,
setHighlightedSourceOptions,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['a', 'x', 'b', 'c'],
})
})

it('should insert multiple values as a block preserving their order', () => {
addSourceOptionsOnDrop({
selected: ['a', 'b'],
draggedValues: ['x', 'y'],
dropIndex: 0,
maxSelections: Infinity,
onChange,
setHighlightedSourceOptions,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['x', 'y', 'a', 'b'],
})
})

it('should clear the source highlights', () => {
addSourceOptionsOnDrop({
selected: [],
draggedValues: ['x'],
maxSelections: Infinity,
onChange,
setHighlightedSourceOptions,
})

expect(setHighlightedSourceOptions).toHaveBeenCalledWith([])
})

it('should evict the oldest values when exceeding maxSelections on append', () => {
addSourceOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['x'],
maxSelections: 3,
onChange,
setHighlightedSourceOptions,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['b', 'c', 'x'],
})
})

it('should never evict the just-dropped values on a positional insert', () => {
addSourceOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['x'],
dropIndex: 0,
maxSelections: 3,
onChange,
setHighlightedSourceOptions,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['x', 'b', 'c'],
})
})

it('should replace the current selection when maxSelections is 1', () => {
addSourceOptionsOnDrop({
selected: ['a'],
draggedValues: ['x'],
dropIndex: 0,
maxSelections: 1,
onChange,
setHighlightedSourceOptions,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['x'],
})
})

it('should clamp a drop index beyond the list length', () => {
addSourceOptionsOnDrop({
selected: ['a'],
draggedValues: ['x'],
dropIndex: 100,
maxSelections: Infinity,
onChange,
setHighlightedSourceOptions,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['a', 'x'],
})
})

it('should do nothing when the dragged set is empty', () => {
addSourceOptionsOnDrop({
selected: ['a'],
draggedValues: [],
maxSelections: Infinity,
onChange,
setHighlightedSourceOptions,
})

expect(onChange).toHaveBeenCalledTimes(0)
expect(setHighlightedSourceOptions).toHaveBeenCalledTimes(0)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { getDraggedValues } from '../../transfer/get-dragged-values.js'

describe('Transfer - getDraggedValues', () => {
const visibleOptions = [
{ label: 'A', value: 'a' },
{ label: 'B', value: 'b' },
{ label: 'C', value: 'c' },
{ label: 'D', value: 'd' },
]

it('should return only the dragged value when it is not highlighted', () => {
expect(
getDraggedValues({
draggedValue: 'b',
highlightedOptions: ['c', 'd'],
visibleOptions,
})
).toEqual(['b'])
})

it('should return only the dragged value when nothing is highlighted', () => {
expect(
getDraggedValues({
draggedValue: 'b',
highlightedOptions: [],
visibleOptions,
})
).toEqual(['b'])
})

it('should return the whole highlighted set when the dragged value is highlighted', () => {
expect(
getDraggedValues({
draggedValue: 'c',
highlightedOptions: ['a', 'c'],
visibleOptions,
})
).toEqual(['a', 'c'])
})

it('should order the dragged values by list order, not highlight order', () => {
expect(
getDraggedValues({
draggedValue: 'a',
highlightedOptions: ['d', 'a', 'b'],
visibleOptions,
})
).toEqual(['a', 'b', 'd'])
})

it('should exclude highlighted values that are not visible (hidden by a filter)', () => {
expect(
getDraggedValues({
draggedValue: 'a',
highlightedOptions: ['a', 'hidden-by-filter'],
visibleOptions,
})
).toEqual(['a'])
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { reorderPickedOptionsOnDrop } from '../../transfer/reorder-picked-options-on-drop.js'

describe('Transfer - reorderPickedOptionsOnDrop', () => {
const onChange = jest.fn()

afterEach(() => {
onChange.mockClear()
})

it('should move a single option down', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c', 'd'],
draggedValues: ['a'],
dropIndex: 3,
onChange,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['b', 'c', 'a', 'd'],
})
})

it('should move a single option up', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c', 'd'],
draggedValues: ['c'],
dropIndex: 1,
onChange,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['a', 'c', 'b', 'd'],
})
})

it('should move an option to the very top', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['c'],
dropIndex: 0,
onChange,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['c', 'a', 'b'],
})
})

it('should move an option to the very end', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['a'],
dropIndex: 3,
onChange,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['b', 'c', 'a'],
})
})

it('should move a contiguous block as a group', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c', 'd', 'e'],
draggedValues: ['b', 'c'],
dropIndex: 5,
onChange,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['a', 'd', 'e', 'b', 'c'],
})
})

it('should collapse a non-contiguous selection into a contiguous block at the drop position', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c', 'd', 'e'],
draggedValues: ['a', 'd'],
dropIndex: 3,
onChange,
})

// 'a' and 'd' are removed, insertion index 3 adjusted by the
// one dragged item ('a') above it -> lands after 'c'
expect(onChange).toHaveBeenCalledWith({
selected: ['b', 'c', 'a', 'd', 'e'],
})
})

it('should preserve list order of dragged values regardless of input order', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c', 'd', 'e'],
draggedValues: ['d', 'b'],
dropIndex: 0,
onChange,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['b', 'd', 'a', 'c', 'e'],
})
})

it('should not call onChange when the drop results in no change', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['b'],
dropIndex: 1,
onChange,
})

expect(onChange).toHaveBeenCalledTimes(0)
})

it('should not call onChange when dropping right below the dragged option', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['b'],
dropIndex: 2,
onChange,
})

expect(onChange).toHaveBeenCalledTimes(0)
})

it('should not call onChange when no dragged value exists in selected', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['ghost'],
dropIndex: 0,
onChange,
})

expect(onChange).toHaveBeenCalledTimes(0)
})

it('should ignore dragged values that do not exist in selected', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['ghost', 'c'],
dropIndex: 0,
onChange,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['c', 'a', 'b'],
})
})

it('should clamp a drop index beyond the list length', () => {
reorderPickedOptionsOnDrop({
selected: ['a', 'b', 'c'],
draggedValues: ['a'],
dropIndex: 100,
onChange,
})

expect(onChange).toHaveBeenCalledWith({
selected: ['b', 'c', 'a'],
})
})
})
Loading
Loading