// TODO: Repair these tests. Broke when passing to test-utils ^1.0.0-beta.28 import { mount, Wrapper, WrapperArray } from '@vue/test-utils'; import Vue from 'vue'; import { resetModulPlugins } from '../../../tests/helpers/component'; import { polyFillActive } from '../../utils/polyfills/drag-and-drop.polyfill'; import { MDOMPlugin } from '../domPlugin'; import { MDraggable, MDraggableOptions } from '../drag-and-drop/draggable/draggable'; import { MDroppable, MDroppableClassNames } from '../drag-and-drop/droppable/droppable'; import DroppableGroupPlugin from '../drag-and-drop/droppable/droppable-group'; import { MSortableDefaultInsertionMarkerBehavior } from './insertion-behavior'; import SortablePlugin, { MSortable, MSortableAction, MSortableClassNames, MSortableOptions, MSortEvent, MSortInfo, MSortInsertPositions } from './sortable'; jest.mock('./insertion-behavior'); let onAddSortInfo: MSortInfo; let onMoveSortInfo: MSortInfo; let onRemoveSortInfo: MSortInfo; describe('sortable', () => { beforeEach(() => { polyFillActive.dragDrop = false; resetModulPlugins(); Vue.use(SortablePlugin); Vue.use(DroppableGroupPlugin); MSortable.fromSortContainer = undefined; MSortable.activeSortContainer = undefined; MDraggable.currentDraggable = undefined; MDroppable.currentHoverDroppable = undefined; onAddSortInfo = undefined as any; onMoveSortInfo = undefined as any; onRemoveSortInfo = undefined as any; }); const emptyPlaceholderTemplate: string = `
  • Item #{{ $index }}
  • `; const getSortableDirective: (bindingValue?: boolean, options?: MSortableOptions, innerHtml?: string) => Wrapper = (bindingValue?: boolean, options?: MSortableOptions, innerHtml?: string) => { const innerHTML: string = `${innerHtml || emptyPlaceholderTemplate}`; let template: string; const handlers: string = `@sortable:add="onAdd" @sortable:move="onMove" @sortable:remove="onRemove"`; if (options) { template = bindingValue === undefined ? `` : ``; } else { template = bindingValue === undefined ? `` : ``; } let directive: Wrapper; directive = mount({ template, methods: { onAdd(params: MSortEvent): void { onAddSortInfo = params.sortInfo; }, onMove(params: MSortEvent): void { onMoveSortInfo = params.sortInfo; }, onRemove(params: MSortEvent): void { onRemoveSortInfo = params.sortInfo; } }, data: () => options || {} }, { localVue: Vue }); return directive; }; const getDraggableDirective: (bindingValue?: boolean, options?: MDraggableOptions, innerHtml?: string) => Wrapper = (bindingValue?: boolean, options?: MDraggableOptions, innerHtml?: string) => { let directive: Wrapper; if (options) { directive = mount({ template: bindingValue === undefined ? `
    ` : `
    `, data: () => options }, { localVue: Vue }); } else { directive = mount({ template: bindingValue === undefined ? `
    ` : `
    ` }, { localVue: Vue }); } return directive; }; const getEventDummy: () => any = () => { return { preventDefault: () => { }, stopPropagation: () => { }, dataTransfer: { setData: () => { }, setDragImage: () => { }, getData: () => { } } }; }; [true, undefined].forEach(param => { it(`should render correctly when binding ${param} is provided`, () => { const userDefinedActions: string[] = ['a', 'b']; const userDefinedItems: any[] = [{ id: 0 }]; const userDefinedEncapsulate: boolean = true; const sortable: Wrapper = getSortableDirective(param, { items: userDefinedItems, acceptedActions: userDefinedActions, encapsulate: userDefinedEncapsulate }); expect(sortable.element.classList).toContain(MSortableClassNames.Sortable); expect(MDOMPlugin.get(MSortable, sortable.element)!.options.acceptedActions) .toEqual([...userDefinedActions, MSortableAction.Move, MSortableAction.MoveGroup]) ; expect(MDOMPlugin.get(MSortable, sortable.element)!.options.items).toBe(userDefinedItems); expect(MDOMPlugin.get(MSortable, sortable.element)!.options.encapsulate).toBe(userDefinedEncapsulate); expect(MDOMPlugin.get(MDroppable, sortable.element)).toBeDefined(); }); it(`should default options correctly when binding ${param} is provided and action is not user defined`, () => { const sortable: Wrapper = getSortableDirective(param); expect(MDOMPlugin.get(MDroppable, sortable.element)!.options.acceptedActions).toEqual([MSortableAction.Default, MSortableAction.Move, MSortableAction.MoveGroup]); expect(MDOMPlugin.get(MSortable, sortable.element)!.options.encapsulate).toBeFalsy(); }); it('should setup empty placeholder correctly', () => { const userDefinedActions: string[] = ['a', 'b']; const items: any[] = [{ id: 0 }]; const sortable: Wrapper = getSortableDirective(param, { acceptedActions: userDefinedActions, items: items }, `
    `); expect(sortable.element.children[0].classList).toContain(MSortableClassNames.EmptyPlaceholder); expect(MDOMPlugin.get(MDraggable, sortable.element)).toBeUndefined(); const droppablePlugin: MDroppable = MDOMPlugin.get(MDroppable, sortable.element.children[0] as HTMLElement)!; expect(droppablePlugin).toBeDefined(); expect(droppablePlugin.options).toEqual({ canDrop: true, acceptedActions: [...userDefinedActions, MSortableAction.Move, MSortableAction.MoveGroup] }); }); it('should setup childs droppable part correctly', () => { const userDefinedActions: string[] = ['a', 'b']; const items: any[] = [{ id: 0 }]; const sortable: Wrapper = getSortableDirective(param, { acceptedActions: userDefinedActions, items: items }); const droppablePlugin: MDroppable = MDOMPlugin.get(MDroppable, sortable.element.children[0] as HTMLElement)!; expect(droppablePlugin).toBeDefined(); expect(droppablePlugin.options).toEqual({ canDrop: true, acceptedActions: [...userDefinedActions, MSortableAction.Move, MSortableAction.MoveGroup], alwaysMount: true }); }); it('should setup childs draggable part correctly', () => { const userDefinedActions: string[] = ['a', 'b']; const items: any[] = [{ id: 0 }]; const sortable: Wrapper = getSortableDirective(param, { acceptedActions: userDefinedActions, items: items }); const draggablePlugin: MDraggable = MDOMPlugin.get(MDraggable, sortable.element.children[0] as HTMLElement)!; expect(draggablePlugin).toBeDefined(); expect(draggablePlugin.options).toEqual({ canDrag: true, action: MSortableAction.Move, dragData: items[0] }); }); it('should setup grouped childs droppable part correctly', () => { const userDefinedActions: string[] = ['a', 'b']; const items: any[] = [{ id: 0 }]; const innerHtml: string = `
  • Item #{{ $index }}
  • `; const sortable: Wrapper = getSortableDirective(param, { acceptedActions: userDefinedActions, items: items }, innerHtml); const droppablePlugin: MDroppable = MDOMPlugin.get(MDroppable, sortable.element.children[0] as HTMLElement)!; expect(droppablePlugin).toBeDefined(); expect(droppablePlugin.options).toEqual({ canDrop: true, acceptedActions: [...userDefinedActions, MSortableAction.Move, MSortableAction.MoveGroup], alwaysMount: true }); }); it('should setup grouped childs draggable part correctly', () => { const userDefinedActions: string[] = ['a', 'b']; const userDefinedGroup: number = 1; const items: any[] = [{ id: 0 }]; const innerHtml: string = `
  • Item #{{ $index }}
  • `; const sortable: Wrapper = getSortableDirective(param, { acceptedActions: userDefinedActions, items: items }, innerHtml); const draggablePlugin: MDraggable = MDOMPlugin.get(MDraggable, sortable.element.children[0] as HTMLElement)!; expect(draggablePlugin).toBeDefined(); expect(draggablePlugin.options).toEqual({ canDrag: true, action: MSortableAction.MoveGroup, dragData: items[0], grouping: userDefinedGroup }); }); }); it('should render correctly when binding false is provided', () => { const sortable: Wrapper = getSortableDirective(false, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); expect(MDOMPlugin.get(MDroppable, sortable.element)).toBeUndefined(); expect(MDOMPlugin.get(MDraggable, sortable.element.children[0] as HTMLElement)).toBeUndefined(); expect(MDOMPlugin.get(MDroppable, sortable.element.children[0] as HTMLElement)).toBeDefined(); }); it('should not attach empty placeholder when binding false is provided', () => { const sortable: Wrapper = getSortableDirective(false, undefined, `
    Some placeholder
    `); expect(MDOMPlugin.get(MDraggable, sortable.element.children[0] as HTMLElement)).toBeUndefined(); expect(MDOMPlugin.get(MDroppable, sortable.element.children[0] as HTMLElement)).toBeUndefined(); }); describe('unbind', () => { it('should clean up element correctly', () => { const sortable: Wrapper = getSortableDirective(true, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); const plugin: MSortable = MDOMPlugin.get(MSortable, sortable.element)!; jest.spyOn(plugin, 'doCleanUp'); sortable.destroy(); expect(sortable.element.classList).not.toContain(MSortableClassNames.Sortable); expect(plugin.doCleanUp).toHaveBeenCalled(); expect(MDOMPlugin.get(MDroppable, sortable.element)).toBeUndefined(); }); it('should clean up empty placeholder when it exists', () => { const sortable: Wrapper = getSortableDirective(true, undefined, `
    Some placeholder
    `); sortable.destroy(); expect(MDOMPlugin.get(MDraggable, sortable.element.children[0] as HTMLElement)).toBeUndefined(); expect(MDOMPlugin.get(MDroppable, sortable.element.children[0] as HTMLElement)).toBeUndefined(); }); it('should clean up childs correctly', () => { const sortable: Wrapper = getSortableDirective(true, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); sortable.destroy(); expect(MDOMPlugin.get(MDraggable, sortable.element.children[0] as HTMLElement)).toBeUndefined(); expect(MDOMPlugin.get(MDroppable, sortable.element.children[0] as HTMLElement)).toBeUndefined(); }); }); describe('onDragEnter', () => { const buildSortable: (options: MSortableOptions, mockDroppable?: boolean) => Wrapper = (options: MSortableOptions, mockDroppable?: boolean) => { const sortable: Wrapper = getSortableDirective(true, options); return sortable; }; it('should handle event correctly', () => { const sortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); sortable.find('li').trigger('dragstart', getEventDummy()); sortable.find('li').trigger('dragenter', getEventDummy()); expect(MSortable.activeSortContainer).toBe(MDOMPlugin.get(MSortable, sortable.element)); }); it('should handle transition from one active sortable to another', () => { const sortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); const secondSortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); sortable.find('li').trigger('dragstart', getEventDummy()); sortable.find('li').trigger('dragenter', getEventDummy()); jest.spyOn(MDOMPlugin.get(MSortable, sortable.element)!, 'doCleanUp'); secondSortable.find('li').trigger('dragenter', getEventDummy()); expect(MDOMPlugin.get(MSortable, sortable.element)!.doCleanUp).toHaveBeenCalled(); expect(MSortable.activeSortContainer).toBe(MDOMPlugin.get(MSortable, secondSortable.element)); }); it('should not call doCleanup more than 1 time on multiple trigger', () => { const sortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); const secondSortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); sortable.find('li').trigger('dragstart', getEventDummy()); jest.spyOn(MDOMPlugin.get(MSortable, sortable.element)!, 'doCleanUp'); MSortable.activeSortContainer = MDOMPlugin.get(MSortable, sortable.element); secondSortable.find('li').trigger('dragenter', getEventDummy()); secondSortable.find('li').trigger('dragenter', getEventDummy()); expect(MDOMPlugin.get(MSortable, sortable.element)!.doCleanUp).toHaveBeenCalledTimes(1); }); it('should transition to sort before class properly', () => { const sortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); const secondSortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); sortable.find('li').trigger('dragstart', getEventDummy()); ((MSortableDefaultInsertionMarkerBehavior as any) as jest.Mock).mockImplementation(() => ({ getInsertPosition: () => MSortInsertPositions.After })); secondSortable.find('li').trigger('dragenter', getEventDummy()); ((MSortableDefaultInsertionMarkerBehavior as any) as jest.Mock).mockImplementation(() => ({ getInsertPosition: () => MSortInsertPositions.Before })); secondSortable.find('li').trigger('dragenter', getEventDummy()); expect(secondSortable.find('li').element.classList).toContain(MSortableClassNames.SortBefore); expect(secondSortable.find('li').element.classList).not.toContain(MSortableClassNames.SortAfter); }); it('should transition to sort after class properly', () => { const sortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); const secondSortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }); sortable.find('li').trigger('dragstart', getEventDummy()); ((MSortableDefaultInsertionMarkerBehavior as any) as jest.Mock).mockImplementation(() => ({ getInsertPosition: () => MSortInsertPositions.Before })); secondSortable.find('li').trigger('dragenter', getEventDummy()); ((MSortableDefaultInsertionMarkerBehavior as any) as jest.Mock).mockImplementation(() => ({ getInsertPosition: () => MSortInsertPositions.After })); secondSortable.find('li').trigger('dragenter', getEventDummy()); expect(secondSortable.find('li').element.classList).toContain(MSortableClassNames.SortAfter); expect(secondSortable.find('li').element.classList).not.toContain(MSortableClassNames.SortBefore); }); it(`should not trigger drop events when dragging from a restricted sortable to another`, () => { const sortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }], encapsulate: true }, false); const secondSortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }, false); sortable.find('li').trigger('dragstart', getEventDummy()); secondSortable.find('li').trigger('dragenter', getEventDummy()); expect(secondSortable.find('li').element.classList).toContain(MDroppableClassNames.CantDrop); }); it(`should not trigger drop events when dragging from a non-restricted sortable to a restricted sortable`, () => { const sortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }] }, false); const secondSortable: Wrapper = buildSortable({ acceptedActions: ['someAction'], items: [{ id: 0 }], encapsulate: true }, false); sortable.find('li').trigger('dragstart', getEventDummy()); secondSortable.find('li').trigger('dragenter', getEventDummy()); expect(secondSortable.find('li').element.classList).toContain(MDroppableClassNames.CantDrop); }); }); describe('onDragLeave', () => { let sortable: Wrapper; let secondSortable: Wrapper; let plugin: MSortable; let secondPlugin: MSortable; beforeEach(() => { sortable = getSortableDirective(true, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); plugin = MDOMPlugin.get(MSortable, sortable.element)!; secondSortable = getSortableDirective(true, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); secondPlugin = MDOMPlugin.get(MSortable, secondSortable.element)!; }); it('should cleanup when leaving sortable', () => { jest.spyOn(plugin, 'doCleanUp'); sortable.find('li').trigger('dragenter', getEventDummy()); sortable.find('li').trigger('dragleave', getEventDummy()); expect(plugin.doCleanUp).toHaveBeenCalled(); expect(MSortable.activeSortContainer).toBeUndefined(); }); it('should cleanup when leaving sortable for another sortable', () => { jest.spyOn(plugin, 'doCleanUp'); sortable.find('li').trigger('dragenter', getEventDummy()); secondSortable.find('li').trigger('dragenter', getEventDummy()); sortable.find('li').trigger('dragleave', getEventDummy()); expect(plugin.doCleanUp).toHaveBeenCalled(); expect(MSortable.activeSortContainer).toBe(secondPlugin); }); }); describe('onDrop', () => { const buildSortable: (options: MSortableOptions) => Wrapper = (options: MSortableOptions) => { const sortable: Wrapper = getSortableDirective(true, options); return sortable; }; it('should handle drop correctly when adding a new item to a sortable', () => { const dragData: any = { id: 0 }; const sortable: Wrapper = buildSortable({ acceptedActions: ['any'], items: [{ id: 0 }] }); const userDefinedAction: string = 'someAction'; const userDefinedGrouping: string = 'someGrouping'; const draggable: Wrapper = getDraggableDirective(true, { action: userDefinedAction, dragData, grouping: userDefinedGrouping }); draggable.trigger('dragstart', getEventDummy()); sortable.find('li').trigger('dragover', getEventDummy()); sortable.find('li').trigger('drop', getEventDummy()); expect(onAddSortInfo).toEqual({ canDrop: true, data: dragData, action: userDefinedAction, oldPosition: -1, newPosition: 1, grouping: userDefinedGrouping }); expect(onMoveSortInfo).toBeUndefined(); expect(onRemoveSortInfo).toBeUndefined(); }); it('should handle drop correctly when moving an item to another index in the same sortable', () => { const dragData: any = { id: 0 }; const sortable: Wrapper = buildSortable({ acceptedActions: ['any'], items: [dragData, { id: 1 }] }); const childWrappers: WrapperArray = sortable.findAll('li'); childWrappers.at(0).trigger('dragstart', getEventDummy()); childWrappers.at(1).trigger('dragover', getEventDummy()); childWrappers.at(1).trigger('drop', getEventDummy()); expect(onAddSortInfo).toBeUndefined(); expect(onMoveSortInfo).toEqual({ canDrop: true, data: dragData, action: 'move', oldPosition: 0, newPosition: 1 }); expect(onRemoveSortInfo).toBeUndefined(); }); it('should handle drop correctly when moving an item from one sortable to another', () => { const dragData: any = { id: 0 }; const sortable: Wrapper = buildSortable({ acceptedActions: ['any'], items: [dragData] }); const secondSortable: Wrapper = buildSortable({ acceptedActions: ['any'], items: [{ id: 1 }] }); sortable.find('li').trigger('dragstart', getEventDummy()); secondSortable.find('li').trigger('dragover', getEventDummy()); secondSortable.find('li').trigger('drop', getEventDummy()); expect(onMoveSortInfo).toEqual({ canDrop: true, data: dragData, action: 'move', oldPosition: -1, newPosition: 1 }); expect(onAddSortInfo).toBeUndefined(); expect(onRemoveSortInfo).toEqual({ canDrop: true, data: dragData, action: 'move', oldPosition: 0, newPosition: -1 }); }); }); describe('onChildDragStart', () => { let sortable: Wrapper; let plugin: MSortable; beforeEach(() => { sortable = getSortableDirective(true, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); plugin = MDOMPlugin.get(MSortable, sortable.element)!; }); it('should manage dragEvent correctly', () => { const dropEventDummy: any = getEventDummy(); sortable.find('li').trigger('dragstart', dropEventDummy); expect(MSortable.fromSortContainer).toBeDefined(); expect(MSortable.fromSortContainer).toBe(plugin); }); }); describe('onChildDragEnd', () => { let sortable: Wrapper; let plugin: MSortable; beforeEach(() => { sortable = getSortableDirective(true, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); plugin = MDOMPlugin.get(MSortable, sortable.element)!; sortable.find('li').trigger('dragstart', getEventDummy()); }); it('should manage dragEvent correctly', () => { const dropEventDummy: any = getEventDummy(); jest.spyOn(plugin, 'doCleanUp'); sortable.find('li').trigger('dragend', dropEventDummy); expect(MSortable.fromSortContainer).toBeUndefined(); expect(plugin.doCleanUp).toHaveBeenCalled(); }); it('should manage dragEvent correctly when there is an active sort container', () => { const secondSortable: Wrapper = getSortableDirective(true, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); secondSortable.find('li').trigger('dragover', getEventDummy()); const secondSortablePlugin: MSortable = MDOMPlugin.get(MSortable, secondSortable.element)!; jest.spyOn(secondSortablePlugin, 'doCleanUp'); sortable.find('li').trigger('dragend', getEventDummy()); expect(secondSortablePlugin.doCleanUp).toHaveBeenCalled(); expect(MSortable.activeSortContainer).toBeUndefined(); }); }); describe('doCleanUp', () => { let sortable: Wrapper; let plugin: MSortable; const addClass: (element: HTMLElement, className: string) => void = (element: HTMLElement, className: string) => { if (!element.classList.contains(className)) { element.classList.add(className); } }; beforeEach(() => { sortable = getSortableDirective(true, { acceptedActions: ['someAction'], items: [{ id: 0 }] }); plugin = MDOMPlugin.get(MSortable, sortable.element)!; }); it('should never clean up Sortable class', () => { addClass(sortable.element, MSortableClassNames.Sortable); plugin.doCleanUp(); expect(sortable.element.classList).toContain(MSortableClassNames.Sortable); }); [MSortableClassNames.SortAfter, MSortableClassNames.SortBefore, MSortableClassNames.SortIn].forEach(className => { it(`should clean up ${className} class when it exists on element`, () => { addClass(sortable.element, className); plugin.doCleanUp(); expect(sortable.element.classList).not.toContain(className); }); it(`should clean up ${className} class when it exists on element child`, () => { addClass(sortable.element.children[0] as HTMLElement, className); plugin.doCleanUp(); expect(sortable.element.querySelector(`.${className}`)).toBeNull(); }); }); }); });