How to trigger change event on slate.js when testing with Selenium or Cypress

2.4k Views Asked by At

I'm trying to find a way to simulate a "change" event when doing E2E testing (with selenium or cypress) and slate.js

In our UI, when the user clicks on a word, we pop-up a modal (related to that word). I've been unable to make this happen as I can't get the change event to trigger

3

There are 3 best solutions below

1
On

Cypress can explicitly trigger events: https://docs.cypress.io/api/commands/trigger.html#Syntax

This may work for you:

cy.get(#element).trigger("change")
0
On

Found a solution:

1) Add a ref to the Editor

 <Editor
   ref={this.editor}
 />

2) Add a document listener for a custom event

componentDidMount() {
    document.addEventListener("Test_SelectWord", this.onTestSelectWord)
}

componentWillUnmount() {
    document.removeEventListener("Test_SelectWord", this.onTestSelectWord)
}

3) Create a handler that creates a custom select event

onTestSelectWord(val: any) {
    let slateEditor = val.detail.parentElement.parentElement.parentElement.parentElement

    // Events are special, can't use spread or Object.keys
    let selectEvent: any = {}
    for (let key in val) { 
        if (key === 'currentTarget') {
            selectEvent['currentTarget'] = slateEditor
        }
        else if (key === 'type') {
            selectEvent['type'] = "select"
        }
        else {
            selectEvent[key] = val[key] 
        }
    }

    // Make selection
    let selection = window.getSelection();        
    let range = document.createRange();
    range.selectNodeContents(val.detail);
    selection.removeAllRanges();
    selection.addRange(range)

    // Fire select event
    this.editor.current.onEvent("onSelect", selectEvent)
}

4) User the following in your test code:

arr = Array.from(document.querySelectorAll(".cl-token-node"))
text = arr.filter(element => element.children[0].innerText === "*WORD_YOU_ARE_SELECTING*")[0].children[0].children[0]
var event = new CustomEvent("Test_SelectWord", {detail: text})
document.dispatchEvent(event, text)
1
On

The Cypress input commands (e.g. cy.type() and cy.clear()) work by dispatching input and change events - in the case of cy.type(), one per character. This mimics the behavior of a real browser as a user types on their keyboard and is enough to trigger the behavior of most application JavaScript.

However, Slate relies almost exclusively on the beforeinput event (see here https://docs.slatejs.org/concepts/xx-migrating#beforeinput) which is a new browser technology and an event which the Cypress input commands don’t simulate. Hopefully the Cypress team will update their input commands to dispatch the beforeinput event, but until they do I’ve created a couple of simple custom commands which will trigger Slate’s input event listeners and make it respond.

// commands.js
Cypress.Commands.add('getEditor', (selector) => {
    return cy.get(selector)
    .click();
});

Cypress.Commands.add('typeInSlate', { prevSubject: true }, (subject, text) => {
    return cy.wrap(subject)
    .then(subject => {
        subject[0].dispatchEvent(new InputEvent('beforeinput', { inputType: 'insertText', data: text }));
        return subject;
    })
});
    
Cypress.Commands.add('clearInSlate', { prevSubject: true }, (subject) => {
    return cy.wrap(subject)
    .then(subject => {
        subject[0].dispatchEvent(new InputEvent('beforeinput', { inputType: 'deleteHardLineBackward' }))
        return subject;
    })
});

// slateEditor.spec.js
cy.getEditor('[data-testid=slateEditor1] [contenteditable]')
    .typeInSlate('Some input text ');

cy.getEditor('[data-testid=slateEditor2] [contenteditable]')
    .clearInSlate()
    .typeInSlate('http://httpbin.org/status/409');

If you need to support other inputTypes, all of the inputTypes supported by Slate are listed in the source code for editable.tsx