@@ -13,11 +13,12 @@ import {
1313 simulateKeyboard ,
1414 simulateLostPointerCapture ,
1515 simulatePointerDown ,
16+ simulatePointerMove ,
1617} from '../utils.spec.js' ;
1718import { addDragController } from './drag.js' ;
1819import { escapeKey } from './key-bindings.js' ;
1920
20- describe ( 'Drag and drop controller' , ( ) => {
21+ describe ( 'Drag controller' , ( ) => {
2122 type DragElement = LitElement & {
2223 controller : ReturnType < typeof addDragController > ;
2324 } ;
@@ -60,15 +61,15 @@ describe('Drag and drop controller', () => {
6061 const root = await fixture ( template ) ;
6162
6263 instance = root . querySelector ( tagName . _$litStatic$ ) ! ;
63- instance . controller . setConfig ( { mode : 'immediate' } ) ;
64+ instance . controller . set ( { mode : 'immediate' } ) ;
6465 } ) ;
6566
6667 afterEach ( ( ) => {
6768 dragStart . resetHistory ( ) ;
6869 } ) ;
6970
7071 it ( 'should not start drag operation when disabled' , async ( ) => {
71- instance . controller . setConfig ( { enabled : false , dragStart } ) ;
72+ instance . controller . set ( { enabled : false , start : dragStart } ) ;
7273
7374 simulatePointerDown ( instance ) ;
7475 await elementUpdated ( instance ) ;
@@ -77,25 +78,27 @@ describe('Drag and drop controller', () => {
7778 } ) ;
7879
7980 it ( 'should not start a drag operation on a non-primary button interaction' , async ( ) => {
80- instance . controller . setConfig ( { dragStart } ) ;
81+ instance . controller . set ( { start : dragStart } ) ;
8182
8283 simulatePointerDown ( instance , { button : 1 } ) ;
8384 await elementUpdated ( instance ) ;
8485
8586 expect ( dragStart . called ) . is . false ;
8687 } ) ;
8788
88- it ( 'should invoke dragStart callback on drag operation' , async ( ) => {
89- instance . controller . setConfig ( { dragStart } ) ;
89+ it ( 'should not start a drag operation when a skip callback returns true' , async ( ) => {
90+ const skip = spy ( ( ) => true ) ;
91+ instance . controller . set ( { skip, start : dragStart } ) ;
9092
9193 simulatePointerDown ( instance ) ;
9294 await elementUpdated ( instance ) ;
9395
94- expect ( dragStart . called ) . is . true ;
96+ expect ( skip . called ) . is . true ;
97+ expect ( dragStart . called ) . is . false ;
9598 } ) ;
9699
97100 it ( 'should apply correct internal styles on drag operation' , async ( ) => {
98- instance . controller . setConfig ( { dragStart } ) ;
101+ instance . controller . set ( { start : dragStart } ) ;
99102 const styles = { touchAction : 'none' , userSelect : 'none' } ;
100103
101104 simulatePointerDown ( instance ) ;
@@ -111,16 +114,278 @@ describe('Drag and drop controller', () => {
111114 expect ( instance . attributeStyleMap . size ) . to . equal ( 0 ) ;
112115 } ) ;
113116
114- it ( 'should not create ghost element in immediate mode' , async ( ) => {
115- instance . controller . setConfig ( { dragStart } ) ;
117+ it ( 'should not create a ghost element in "immediate" mode' , async ( ) => {
118+ const ghost = spy ( ) ;
119+ instance . controller . set ( { start : dragStart , ghost } ) ;
120+
121+ simulatePointerDown ( instance ) ;
122+ await elementUpdated ( instance ) ;
123+
124+ expect ( dragStart . called ) . is . true ;
125+ expect ( ghost . called ) . is . false ;
126+ } ) ;
127+
128+ it ( 'should not invoke the `layer` configuration callback in "immediate" mode' , async ( ) => {
129+ const layer = spy ( ) ;
130+ instance . controller . set ( { start : dragStart , layer } ) ;
131+
132+ simulatePointerDown ( instance ) ;
133+ await elementUpdated ( instance ) ;
134+
135+ expect ( dragStart . called ) . is . true ;
136+ expect ( layer . called ) . is . false ;
137+ } ) ;
138+
139+ it ( 'should invoke start callback on drag operation' , async ( ) => {
140+ instance . controller . set ( { start : dragStart } ) ;
141+
142+ simulatePointerDown ( instance ) ;
143+ await elementUpdated ( instance ) ;
144+
145+ expect ( dragStart . called ) . is . true ;
146+ expect ( dragStart . callCount ) . to . equal ( 1 ) ;
147+ } ) ;
148+
149+ it ( 'should not invoke move unless a start is invoked' , async ( ) => {
150+ const dragMove = spy ( ) ;
151+ instance . controller . set ( { start : dragStart , move : dragMove } ) ;
152+
153+ simulatePointerMove ( instance ) ;
154+ await elementUpdated ( instance ) ;
155+
156+ expect ( dragStart . called ) . is . false ;
157+ expect ( dragMove . called ) . is . false ;
158+ } ) ;
159+
160+ it ( 'should invoke move when moving the dragged element around the viewport' , async ( ) => {
161+ const dragMove = spy ( ) ;
162+ instance . controller . set ( { start : dragStart , move : dragMove } ) ;
163+
164+ simulatePointerDown ( instance ) ;
165+ simulatePointerMove (
166+ instance ,
167+ { clientX : 200 , clientY : 200 } ,
168+ { x : 10 , y : 10 } ,
169+ 10
170+ ) ;
171+ await elementUpdated ( instance ) ;
172+
173+ expect ( dragStart . called ) . is . true ;
174+ expect ( dragMove . called ) . is . true ;
175+ expect ( dragMove . callCount ) . to . equal ( 10 ) ;
176+ } ) ;
177+
178+ it ( 'should invoke end when releasing the dragged element' , async ( ) => {
179+ const dragEnd = spy ( ) ;
180+ instance . controller . set ( { start : dragStart , end : dragEnd } ) ;
116181
117182 simulatePointerDown ( instance ) ;
183+ simulateLostPointerCapture ( instance ) ;
118184 await elementUpdated ( instance ) ;
185+
186+ expect ( dragStart . callCount ) . to . equal ( 1 ) ;
187+ expect ( dragEnd . callCount ) . to . equal ( 1 ) ;
119188 } ) ;
120189
121- it ( 'should fire dragCancel when pressing Escape during a drag operation' , async ( ) => {
190+ it ( 'should invoke cancel when pressing Escape during a drag operation' , async ( ) => {
122191 const dragCancel = spy ( ) ;
123- instance . controller . setConfig ( { dragStart, dragCancel } ) ;
192+ instance . controller . set ( { start : dragStart , cancel : dragCancel } ) ;
193+
194+ simulatePointerDown ( instance ) ;
195+ await elementUpdated ( instance ) ;
196+
197+ expect ( dragStart . called ) . is . true ;
198+
199+ simulateKeyboard ( instance , escapeKey ) ;
200+ await elementUpdated ( instance ) ;
201+
202+ expect ( dragCancel . called ) . is . true ;
203+ } ) ;
204+
205+ it ( 'should not invoke cancel when pressing Escape outside of drag operation' , async ( ) => {
206+ // Sanity check since the Escape key handler is a root level dynamic listener.
207+ const dragCancel = spy ( ) ;
208+ instance . controller . set ( { cancel : dragCancel } ) ;
209+
210+ simulateKeyboard ( instance , escapeKey ) ;
211+ await elementUpdated ( instance ) ;
212+
213+ expect ( dragCancel . called ) . is . false ;
214+ } ) ;
215+ } ) ;
216+
217+ describe ( 'Deferred mode - basic element dragging' , ( ) => {
218+ const dragStart = spy ( ) ;
219+
220+ function createGhost ( ) {
221+ const clone = instance . cloneNode ( ) as HTMLElement ;
222+ clone . setAttribute ( 'data-deferred-drag-ghost' , '' ) ;
223+
224+ return clone ;
225+ }
226+
227+ function getDefaultGhost ( ) {
228+ return document . querySelector ( 'section' ) ?. querySelector ( 'div' ) ! ;
229+ }
230+
231+ function getCustomGhost ( ) {
232+ return document . querySelector < HTMLElement > ( '[data-deferred-drag-ghost]' ) ! ;
233+ }
234+
235+ beforeEach ( async ( ) => {
236+ const tagName = unsafeStatic ( tag ) ;
237+
238+ const template = html `
239+ < section style ="width: 1000px; height: 1000px ">
240+ < ${ tagName } > </ ${ tagName } >
241+ </ section >
242+ ` ;
243+
244+ const root = await fixture ( template ) ;
245+
246+ instance = root . querySelector ( tagName . _$litStatic$ ) ! ;
247+ instance . controller . set ( { mode : 'deferred' } ) ;
248+ } ) ;
249+
250+ afterEach ( ( ) => {
251+ dragStart . resetHistory ( ) ;
252+ } ) ;
253+
254+ it ( 'should not start drag operation when disabled' , async ( ) => {
255+ instance . controller . set ( { enabled : false , start : dragStart } ) ;
256+
257+ simulatePointerDown ( instance ) ;
258+ await elementUpdated ( instance ) ;
259+
260+ expect ( dragStart . called ) . is . false ;
261+ } ) ;
262+
263+ it ( 'should not start a drag operation on a non-primary button interaction' , async ( ) => {
264+ instance . controller . set ( { start : dragStart } ) ;
265+
266+ simulatePointerDown ( instance , { button : 1 } ) ;
267+ await elementUpdated ( instance ) ;
268+
269+ expect ( dragStart . called ) . is . false ;
270+ } ) ;
271+
272+ it ( 'should not start a drag operation when a skip callback returns true' , async ( ) => {
273+ const skip = spy ( ( ) => true ) ;
274+ instance . controller . set ( { skip, start : dragStart } ) ;
275+
276+ simulatePointerDown ( instance ) ;
277+ await elementUpdated ( instance ) ;
278+
279+ expect ( skip . called ) . is . true ;
280+ expect ( dragStart . called ) . is . false ;
281+ } ) ;
282+
283+ it ( 'should apply correct internal styles on drag operation' , async ( ) => {
284+ instance . controller . set ( { start : dragStart } ) ;
285+ const styles = { touchAction : 'none' , userSelect : 'none' } ;
286+
287+ simulatePointerDown ( instance ) ;
288+ await elementUpdated ( instance ) ;
289+
290+ // Default internal styles are touch-action: none & user-select: none while in drag mode.
291+ expect ( instance . attributeStyleMap . size ) . to . equal ( 2 ) ;
292+ expect ( compareStyles ( instance , styles ) ) . is . true ;
293+
294+ simulateLostPointerCapture ( instance ) ;
295+ await elementUpdated ( instance ) ;
296+
297+ expect ( instance . attributeStyleMap . size ) . to . equal ( 0 ) ;
298+ } ) ;
299+
300+ it ( 'should create a default ghost element in "deferred" mode if no configuration is passed' , async ( ) => {
301+ simulatePointerDown ( instance ) ;
302+ await elementUpdated ( instance ) ;
303+
304+ const defaultGhost = getDefaultGhost ( ) ;
305+
306+ expect ( defaultGhost ) . to . exist ;
307+ expect ( defaultGhost . getBoundingClientRect ( ) ) . to . eql (
308+ instance . getBoundingClientRect ( )
309+ ) ;
310+ } ) ;
311+
312+ it ( 'should create a custom ghost element in "deferred" mode when a configuration is passed' , async ( ) => {
313+ instance . controller . set ( { ghost : createGhost } ) ;
314+
315+ simulatePointerDown ( instance ) ;
316+ await elementUpdated ( instance ) ;
317+
318+ const customGhost = getCustomGhost ( ) ;
319+
320+ expect ( customGhost ) . to . exist ;
321+ expect ( customGhost . localName ) . to . equal ( instance . localName ) ;
322+ } ) ;
323+
324+ it ( 'should correctly place the ghost element in the configured layer container' , async ( ) => {
325+ const layer = spy ( ( ) => instance . parentElement ! ) ;
326+ instance . controller . set ( { layer } ) ;
327+
328+ simulatePointerDown ( instance ) ;
329+ await elementUpdated ( instance ) ;
330+
331+ expect ( getDefaultGhost ( ) . parentElement ) . to . eql ( instance . parentElement ) ;
332+ } ) ;
333+
334+ it ( 'should invoke start callback on drag operation' , async ( ) => {
335+ instance . controller . set ( { start : dragStart } ) ;
336+
337+ simulatePointerDown ( instance ) ;
338+ await elementUpdated ( instance ) ;
339+
340+ expect ( dragStart . called ) . is . true ;
341+ expect ( dragStart . callCount ) . to . equal ( 1 ) ;
342+ } ) ;
343+
344+ it ( 'should not invoke move unless a start is invoked' , async ( ) => {
345+ const dragMove = spy ( ) ;
346+ instance . controller . set ( { start : dragStart , move : dragMove } ) ;
347+
348+ simulatePointerMove ( instance ) ;
349+ await elementUpdated ( instance ) ;
350+
351+ expect ( dragStart . called ) . is . false ;
352+ expect ( dragMove . called ) . is . false ;
353+ } ) ;
354+
355+ it ( 'should invoke move when moving the dragged element around the viewport' , async ( ) => {
356+ const dragMove = spy ( ) ;
357+ instance . controller . set ( { start : dragStart , move : dragMove } ) ;
358+
359+ simulatePointerDown ( instance ) ;
360+ simulatePointerMove (
361+ instance ,
362+ { clientX : 200 , clientY : 200 } ,
363+ { x : 10 , y : 10 } ,
364+ 10
365+ ) ;
366+ await elementUpdated ( instance ) ;
367+
368+ expect ( dragStart . called ) . is . true ;
369+ expect ( dragMove . called ) . is . true ;
370+ expect ( dragMove . callCount ) . to . equal ( 10 ) ;
371+ } ) ;
372+
373+ it ( 'should invoke end when releasing the dragged element' , async ( ) => {
374+ const dragEnd = spy ( ) ;
375+ instance . controller . set ( { start : dragStart , end : dragEnd } ) ;
376+
377+ simulatePointerDown ( instance ) ;
378+ simulateLostPointerCapture ( instance ) ;
379+ await elementUpdated ( instance ) ;
380+
381+ expect ( dragStart . callCount ) . to . equal ( 1 ) ;
382+ expect ( dragEnd . callCount ) . to . equal ( 1 ) ;
383+ expect ( getDefaultGhost ( ) ) . is . null ;
384+ } ) ;
385+
386+ it ( 'should invoke cancel when pressing Escape during a drag operation' , async ( ) => {
387+ const dragCancel = spy ( ( ) => instance . controller . dispose ( ) ) ;
388+ instance . controller . set ( { start : dragStart , cancel : dragCancel } ) ;
124389
125390 simulatePointerDown ( instance ) ;
126391 await elementUpdated ( instance ) ;
@@ -131,6 +396,18 @@ describe('Drag and drop controller', () => {
131396 await elementUpdated ( instance ) ;
132397
133398 expect ( dragCancel . called ) . is . true ;
399+ expect ( getDefaultGhost ( ) ) . is . null ;
400+ } ) ;
401+
402+ it ( 'should not invoke cancel when pressing Escape outside of drag operation' , async ( ) => {
403+ // Sanity check since the Escape key handler is a root level dynamic listener.
404+ const dragCancel = spy ( ) ;
405+ instance . controller . set ( { cancel : dragCancel } ) ;
406+
407+ simulateKeyboard ( instance , escapeKey ) ;
408+ await elementUpdated ( instance ) ;
409+
410+ expect ( dragCancel . called ) . is . false ;
134411 } ) ;
135412 } ) ;
136413} ) ;
0 commit comments