-
-
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); | |
} |
Any reason you can think of that these commands don't work properly in headless?
FYI, I couldn't get this working with input
or textarea
's and it looks like its due to lack of support: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects (it also wasn't working in Chrome 80 for me), so I created a fork of the gist with a different path for those elements:
https://gist.github.com/samtsai/5cf901ba61fd8d44c8dd7eaa728cac49
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
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 awesome, thanks for sharing! I think you've got a vestigial
nodes
on line 101.