@@ -37,78 +37,120 @@
*/
// Low level command reused by `setSelection` and low level command `setCursor`
Cypress . Commands . add ( ' selection' , { prevSubject : true } , ( subject , fn ) => {
Cypress . Commands . add ( " selection" , { prevSubject : true } , ( subject , fn ) => {
cy . wrap ( subject )
. trigger ( ' mousedown' )
. trigger ( " mousedown" )
. then ( fn )
. trigger ( ' mouseup' ) ;
. trigger ( " mouseup" )
cy . document ( ) . trigger ( 'selectionchange' ) ;
return cy . wrap ( subject ) ;
} ) ;
Cypress . Commands . add ( 'setSelection' , { prevSubject : true } , ( subject , query , endQuery ) => {
cy . document ( ) . trigger ( "selectionchange" )
return cy . wrap ( subject )
. selection ( $el => {
if ( typeof query === 'string' ) {
const anchorNode = getTextNode ( $el [ 0 ] , query ) ;
const focusNode = endQuery ? getTextNode ( $el [ 0 ] , endQuery ) : anchorNode ;
const anchorOffset = anchorNode . wholeText . indexOf ( query ) ;
const focusOffset = endQuery ?
focusNode . wholeText . indexOf ( endQuery ) + endQuery . length :
anchorOffset + query . length ;
setBaseAndExtent ( anchorNode , anchorOffset , focusNode , focusOffset ) ;
} else if ( typeof query === 'object' ) {
const el = $el [ 0 ] ;
const anchorNode = getTextNode ( el . querySelector ( query . anchorQuery ) ) ;
const anchorOffset = query . anchorOffset || 0 ;
const focusNode = query . focusQuery ? getTextNode ( el . querySelector ( query . focusQuery ) ) : anchorNode ;
const focusOffset = query . focusOffset || 0 ;
setBaseAndExtent ( anchorNode , anchorOffset , focusNode , focusOffset ) ;
} )
Cypress . Commands . add (
"setSelection" ,
{ prevSubject : true } ,
( subject , query , endQuery ) => {
return cy . wrap ( subject ) . selection ( $el => {
const el = $el [ 0 ]
if ( isInputOrTextArea ( el ) ) {
const text = $el . text ( )
const start = text . indexOf ( query )
const end = endQuery
? text . indexOf ( endQuery ) + endQuery . length
: start + query . length
$el [ 0 ] . setSelectionRange ( start , end )
} else if ( typeof query === "string" ) {
const anchorNode = getTextNode ( el , query )
const focusNode = endQuery ? getTextNode ( el , endQuery ) : anchorNode
const anchorOffset = anchorNode . wholeText . indexOf ( query )
const focusOffset = endQuery
? focusNode . wholeText . indexOf ( endQuery ) + endQuery . length
: anchorOffset + query . length
setBaseAndExtent ( anchorNode , anchorOffset , focusNode , focusOffset )
} else if ( typeof query === "object" ) {
const anchorNode = getTextNode ( el . querySelector ( query . anchorQuery ) )
const anchorOffset = query . anchorOffset || 0
const focusNode = query . focusQuery
? getTextNode ( el . querySelector ( query . focusQuery ) )
: anchorNode
const focusOffset = query . focusOffset || 0
setBaseAndExtent ( anchorNode , anchorOffset , focusNode , focusOffset )
}
} ) ;
} ) ;
} )
}
)
// Low level command reused by `setCursorBefore` and `setCursorAfter`, equal to `setCursorAfter`
Cypress . Commands . add ( 'setCursor' , { prevSubject : true } , ( subject , query , atStart ) => {
return cy . wrap ( subject )
. selection ( $el => {
const node = getTextNode ( $el [ 0 ] , query ) ;
const offset = node . wholeText . indexOf ( query ) + ( atStart ? 0 : query . length ) ;
const document = node . ownerDocument ;
document . getSelection ( ) . removeAllRanges ( ) ;
document . getSelection ( ) . collapse ( node , offset ) ;
Cypress . Commands . add (
"setCursor" ,
{ prevSubject : true } ,
( subject , query , atStart ) => {
return cy . wrap ( subject ) . selection ( $el => {
const el = $el [ 0 ]
if ( isInputOrTextArea ( el ) ) {
const text = $el . text ( )
const position = text . indexOf ( query ) + ( atStart ? 0 : query . length )
$el [ 0 ] . setSelectionRange ( position , position )
} else {
const node = getTextNode ( el , query )
const offset =
node . wholeText . indexOf ( query ) + ( atStart ? 0 : query . length )
const document = node . ownerDocument
document . getSelection ( ) . removeAllRanges ( )
document . getSelection ( ) . collapse ( node , offset )
}
} )
// Depending on what you're testing, you may need to chain a `.click()` here to ensure
// further commands are picked up by whatever you're testing (this was required for Slate, for example).
} ) ;
}
)
Cypress . Commands . add ( 'setCursorBefore' , { prevSubject : true } , ( subject , query ) => {
cy . wrap ( subject ) . setCursor ( query , true ) ;
} ) ;
Cypress . Commands . add (
"setCursorBefore" ,
{ prevSubject : true } ,
( subject , query ) => {
cy . wrap ( subject ) . setCursor ( query , true )
}
)
Cypress . Commands . add ( 'setCursorAfter' , { prevSubject : true } , ( subject , query ) => {
cy . wrap ( subject ) . setCursor ( query ) ;
} ) ;
Cypress . Commands . add (
"setCursorAfter" ,
{ prevSubject : true } ,
( subject , query ) => {
cy . wrap ( subject ) . setCursor ( query )
}
)
// Helper functions
function getTextNode ( el , match ) {
const walk = document . createTreeWalker ( el , NodeFilter . SHOW_TEXT , null , false ) ;
function getTextNode ( el , match ) {
const walk = document . createTreeWalker ( el , NodeFilter . SHOW_TEXT , null , false )
if ( ! match ) {
return walk . nextNode ( ) ;
return walk . nextNode ( )
}
const nodes = [ ] ;
let node ;
while ( node = walk . nextNode ( ) ) {
let node
while ( ( node = walk . nextNode ( ) ) ) {
if ( node . wholeText . includes ( match ) ) {
return node ;
return node
}
}
}
function setBaseAndExtent ( ...args ) {
const document = args [ 0 ] . ownerDocument ;
document . getSelection ( ) . removeAllRanges ( ) ;
document . getSelection ( ) . setBaseAndExtent ( ...args ) ;
}
const node = args [ 0 ]
const document = node . ownerDocument
const selection = document . getSelection ( )
selection . setBaseAndExtent ( ...args )
}
function isInputOrTextArea ( el ) {
return (
el instanceof HTMLInputElement ||
el instanceof HTMLTextAreaElement ||
el . nodeName === "TEXTAREA" ||
el . nodeName === "INPUT"
)
}