-
-
Save erquhart/37bf2d938ab594058e0572ed17d3837a to your computer and use it in GitHub Desktop.
/** | |
* Credits | |
* @Bkucera: https://github.com/cypress-io/cypress/issues/2839#issuecomment-447012818 | |
* @Phrogz: https://stackoverflow.com/a/10730777/1556245 | |
* | |
* Usage | |
* ``` | |
* // Types "foo" and then selects "fo" | |
* cy.get('input') | |
* .type('foo') | |
* .setSelection('fo') | |
* | |
* // Types "foo", "bar", "baz", and "qux" on separate lines, then selects "foo", "bar", and "baz" | |
* cy.get('textarea') | |
* .type('foo{enter}bar{enter}baz{enter}qux{enter}') | |
* .setSelection('foo', 'baz') | |
* | |
* // Types "foo" and then sets the cursor before the last letter | |
* cy.get('input') | |
* .type('foo') | |
* .setCursorAfter('fo') | |
* | |
* // Types "foo" and then sets the cursor at the beginning of the word | |
* cy.get('input') | |
* .type('foo') | |
* .setCursorBefore('foo') | |
* | |
* // `setSelection` can alternatively target starting and ending nodes using query strings, | |
* // plus specific offsets. The queries are processed via `Element.querySelector`. | |
* cy.get('body') | |
* .setSelection({ | |
* anchorQuery: 'ul > li > p', // required | |
* anchorOffset: 2 // default: 0 | |
* focusQuery: 'ul > li > p:last-child', // default: anchorQuery | |
* focusOffset: 0 // default: 0 | |
* }) | |
*/ | |
// Low level command reused by `setSelection` and low level command `setCursor` | |
Cypress.Commands.add('selection', { prevSubject: true }, (subject, fn) => { | |
cy.wrap(subject) | |
.trigger('mousedown') | |
.then(fn) | |
.trigger('mouseup'); | |
cy.document().trigger('selectionchange'); | |
return cy.wrap(subject); | |
}); | |
Cypress.Commands.add('setSelection', { prevSubject: true }, (subject, query, endQuery) => { | |
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); | |
} | |
}); | |
}); | |
// 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); | |
}) | |
// 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('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); | |
if (!match) { | |
return walk.nextNode(); | |
} | |
const nodes = []; | |
let node; | |
while(node = walk.nextNode()) { | |
if (node.wholeText.includes(match)) { | |
return node; | |
} | |
} | |
} | |
function setBaseAndExtent(...args) { | |
const document = args[0].ownerDocument; | |
document.getSelection().removeAllRanges(); | |
document.getSelection().setBaseAndExtent(...args); | |
} |
does this (or the newer solution here in the comments) work on plain text in the DOM? (and not only on input)
for example if I have:
<p> this is the text I have <p>
can I select "is the"?
When I chain setSelection, setCursorBefore or setCursorAfter with .type() there is a 50/50 chance that .type() will occur first. Is there a way to fix this?
Thanks ! It works well for what I needed
guys don't waste your time by trying to use the code. it throws the following error:
TypeError: Cannot read property 'wholeText' of undefined
@alexey-sh Were you able to solve this issue? I am facing the same. If you solved it, can you please tell me what changes did you make?
@rahulworks-git I don't remember exact way to solve it but I think it was fixed somehow. May by I decided to skip this kind of tests.
@kolpav thanks for the note, maybe you are right. but in the gist I can see
I guess
'input'
selector means plain input.