diff --git a/addon/addon-test-support/@ember/test-helpers/dom/drag-move.ts b/addon/addon-test-support/@ember/test-helpers/dom/drag-move.ts new file mode 100644 index 000000000..fa2a765d7 --- /dev/null +++ b/addon/addon-test-support/@ember/test-helpers/dom/drag-move.ts @@ -0,0 +1,81 @@ +import { settled } from '..'; +import { registerHook, runHooks } from '../helper-hooks'; +import getElement from './-get-element'; +import { log } from './-logging'; +import Target from './-target'; +import fireEvent from './fire-event'; + +registerHook( + 'dragMove', + 'start', + (draggableElement: Target, targetElement: Target) => { + log('dragMove', draggableElement, targetElement); + } +); + +/** + Drag and drop + + Simulates a drag and drop movement from a draggable source element that is dropped on a drop target element + + @public + @param {element} draggableStartElement - the element or selector that the drag and drop begins on + @param {element} dropTargetElement - element the element or selector that receives the drop + @returns {promise} returns event with dataTransfer object +*/ +export default function dragMove( + draggableStartElement: Target, + dropTargetElement: Target +): Promise { + const context: Record = {}; + + if (!draggableStartElement) { + throw new Error( + `Element not found: ${draggableStartElement}. Must pass an element or selector.` + ); + } + if (!dropTargetElement) { + throw new Error( + `Element not found: ${dropTargetElement}. Must pass an element or selector.` + ); + } + + const dragEl = getElement(draggableStartElement) as Element | HTMLElement; + + const targetEl = getElement(dropTargetElement) as Element | HTMLElement; + + return Promise.resolve() + .then(() => + runHooks('dragMove', 'start', draggableStartElement, dropTargetElement) + ) + .then(async () => { + fireEvent(dragEl, 'dragstart', { + dataTransfer: { + setData(someKey: string, value: string) { + context[someKey] = value; + }, + }, + }) + .then(() => { + fireEvent(targetEl, 'dragenter'); + }) + .then(() => { + fireEvent(targetEl, 'dragover'); + }) + .then(() => { + fireEvent(targetEl, 'drop', { + dataTransfer: { + getData(someKey: string) { + return context[someKey]; + }, + }, + }); + }) + .then(() => { + return settled(); + }); + }) + .then(() => + runHooks('dragMove', 'end', draggableStartElement, dropTargetElement) + ); +} diff --git a/addon/addon-test-support/@ember/test-helpers/dom/dragMove.ts b/addon/addon-test-support/@ember/test-helpers/dom/dragMove.ts new file mode 100644 index 000000000..74f1b8ad4 --- /dev/null +++ b/addon/addon-test-support/@ember/test-helpers/dom/dragMove.ts @@ -0,0 +1,73 @@ +import { settled } from '..'; +import { registerHook, runHooks } from '../helper-hooks'; +import getElement from './-get-element'; +import { log } from './-logging'; +import Target from './-target'; +import fireEvent from './fire-event'; + +registerHook('fillIn', 'start', (target: Target, text: string) => { + log('fillIn', target, text); +}); + +/** + Drag and drop + + Simulates a drag and drop movement from a draggable source element that is dropped on a drop target element + + @public + @param {element} draggableStartElement - the element or selector that the drag and drop begins on + @param {element} dropTargetElement - element the element or selector that receives the drop + @returns {promise} returns event with dataTransfer object +*/ +export default function dragMove( + draggableStartElement: Target, + dropTargetElement: Target +): Promise { + const context: Record = {}; + + return Promise.resolve() + .then(() => + runHooks('dragMove', 'start', draggableStartElement, dropTargetElement) + ) + .then(() => { + if (!draggableStartElement) { + throw new Error( + `Element not found: ${draggableStartElement}. Must pass an element or selector.` + ); + } + if (!dropTargetElement) { + throw new Error( + `Element not found: ${dropTargetElement}. Must pass an element or selector.` + ); + } + + const dragEl = getElement(draggableStartElement) as Element | HTMLElement; + + const targetEl = getElement(draggableStartElement) as + | Element + | HTMLElement; + + fireEvent(dragEl, 'dragstart', { + dataTransfer: { + setData(someKey: string, value: string) { + context[someKey] = value; + }, + }, + }); + + fireEvent(targetEl, 'dragover'); + + fireEvent(targetEl, 'drop', { + dataTransfer: { + getData(someKey: string) { + return context[someKey]; + }, + }, + }); + + return settled(); + }) + .then(() => + runHooks('moveDrag', 'end', draggableStartElement, dropTargetElement) + ); +} diff --git a/addon/addon-test-support/@ember/test-helpers/dom/fire-event.ts b/addon/addon-test-support/@ember/test-helpers/dom/fire-event.ts index dee0b716e..ed7248dec 100644 --- a/addon/addon-test-support/@ember/test-helpers/dom/fire-event.ts +++ b/addon/addon-test-support/@ember/test-helpers/dom/fire-event.ts @@ -31,14 +31,21 @@ export function isKeyboardEventType( const MOUSE_EVENT_TYPES = tuple( 'click', - 'mousedown', - 'mouseup', 'dblclick', + 'drag', + 'dragend', + 'dragenter', + 'dragleave', + 'dragover', + 'dragstart', + 'drop', + 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', - 'mouseover' + 'mouseover', + 'mouseup' ); export type MouseEventType = (typeof MOUSE_EVENT_TYPES)[number]; @@ -200,7 +207,6 @@ function buildMouseEvent(type: MouseEventType, options: any = {}) { event = buildBasicEvent(type, options); } } - return event; } diff --git a/addon/addon-test-support/@ember/test-helpers/index.ts b/addon/addon-test-support/@ember/test-helpers/index.ts index c547d687e..b9b288c9c 100644 --- a/addon/addon-test-support/@ember/test-helpers/index.ts +++ b/addon/addon-test-support/@ember/test-helpers/index.ts @@ -74,6 +74,8 @@ export { default as find } from './dom/find'; export { default as findAll } from './dom/find-all'; export { default as typeIn } from './dom/type-in'; export { default as scrollTo } from './dom/scroll-to'; +export { default as dragMove } from './dom/drag-move'; + export type { default as Target } from './dom/-target'; // Declaration-merge for our internal purposes. diff --git a/addon/tests/integration/dom/drag-move-test.js b/addon/tests/integration/dom/drag-move-test.js new file mode 100644 index 000000000..c90e11dde --- /dev/null +++ b/addon/tests/integration/dom/drag-move-test.js @@ -0,0 +1,117 @@ +import { module, test } from 'qunit'; +import { hbs } from 'ember-cli-htmlbars'; +import { + dragMove, + find, + render, + setupContext, + setupRenderingContext, + teardownContext, +} from '@ember/test-helpers'; +import hasEmberVersion from '@ember/test-helpers/has-ember-version'; + +module('DOM Helper: moveDrag', function (hooks) { + if (!hasEmberVersion(2, 4)) { + return; + } + + hooks.beforeEach(async function () { + await setupContext(this); + await setupRenderingContext(this); + }); + + hooks.afterEach(async function () { + await teardownContext(this); + }); + + test('Drag and drop triggers native events', async function (assert) { + this.set('dragstartTriggered', false); + this.set('dragoverTriggered', false); + this.set('dragenterTriggered', false); + this.set('dropTriggered', false); + + this.set('handleDragStart', () => { + this.set('dragStarted', true); + }); + this.set('handleDragOver', (event) => { + // Per docs, you should prevent default on over and enter in order + // for drop zone to be a valid target + event.preventDefault(); + this.set('dragoverTriggered', true); + }); + this.set('handleDragEnter', (event) => { + event.preventDefault(); + this.set('dragenterTriggered', true); + }); + this.set('handleDrop', () => { + console.log('dropped'); + this.set('dropTriggered', true); + }); + + await render(hbs` +
Test draggable text
+
Drag text here
+ `); + + /* The below addEventListeners are used because Ember 3.8 did not have the `on` helper, and + * this test errors in ember-test-helpers github ci. + * + * This is how the dom rendering above would loook like with modern ember templates: + * + *
Test draggable text
+ *
Drag text here
+ */ + const sourceEl = find('.source'); + sourceEl.addEventListener('dragstart', this.handleDragStart); + const targetEl = find('.target'); + targetEl.addEventListener('drop', this.handleDrop); + targetEl.addEventListener('dragover', this.handleDragOver); + targetEl.addEventListener('dragenter', this.handleDragEnter); + + await dragMove('.source', '.target'); + + assert.true(this.get('dragStarted')); + assert.true(this.get('dragenterTriggered')); + assert.true(this.get('dragoverTriggered')); + assert.true(this.get('dropTriggered')); + }); + + test('dataTransfer mock transfers data like html api', async function (assert) { + const testString = 'Test string!'; + + this.set('handleDragStart', (event) => { + event.dataTransfer.setData('test', testString); + }); + this.set('handleDragOver', (event) => { + event.preventDefault(); + }); + this.set('handleDragEnter', (event) => { + event.preventDefault(); + }); + this.set('handleDrop', (event) => { + const payload = event.dataTransfer.getData('test'); + + this.set('deliveredPayload', payload); + }); + + await render(hbs` +
Test draggable text
+
Drag text here
+ `); + + const sourceEl = find('.source'); + sourceEl.addEventListener('dragstart', this.handleDragStart); + const targetEl = find('.target'); + targetEl.addEventListener('drop', this.handleDrop); + targetEl.addEventListener('dragover', this.handleDragOver); + targetEl.addEventListener('dragenter', this.handleDragEnter); + + await dragMove('.source', '.target'); + + assert.equal(testString, this.get('deliveredPayload')); + }); +});