-
-
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); | |
} |
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 These functions are meant to be used with contenteditable
not plain inputs thats why you are getting wholeText
undefined error.
@kolpav thanks for the note, maybe you are right. but in the gist I can see
* // Types "foo" and then sets the cursor at the beginning of the word
* cy.get('input')
* .type('foo')
* .setCursorBefore('foo')
I guess 'input'
selector means plain input.
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.
Thank you, friend. I've been trying to highlight the damn text in the cypress tree for two days.
Astonished that this is still useful!
This is old, here's our latest implementation as of this writing (you'll have to read a bit to pick out the relevant code): https://github.com/netlify/netlify-cms/blob/a4b7481a99f58b9abe85ab5712d27593cde20096/cypress/support/commands.js#L180