reactjscypresscypress-component-test-runnerlexicaljs

Component-testing @lexical/react editor: How to assert against HTML rendered by Cypress?


I want to component-test a controlled @lexical/react based input in Cypress. An example scenario:

Given I have a controlled input with initial state "Hello world"

When I click into the input 
And I press "! key" + "Home key" + "Arrow right key" + "Arrow right key" + "n key"

Then input content is "Henlo world!"    

How can I get the editor's text content, so I can assert against it?

I was hoping I could attach a newly-created editor instance to the rendered content to gain access to native lexical methods:

it('Works as intended', () => {
    const Test: FC = () => {
        const [state, setState] = useState('Hello world')
        
        return <MyInput value={state} setValue={setState} />
    }
    
    mount(<Test />)
    
    cy.get('[contenteditable="true"]').should(editorRoot => {
        const editorHTML = editorRoot.get()[0]                
        const editorHtmlClone = editorHTML.cloneNode(true)    
        const editor = createEditor()
        editor.setRootElement(editorHtmlClone as any)         
        editor.getEditorState().read(() => {
            const textValue = $getRoot().getTextContent()
            // Now I can access the editor's value
            // but unfortunately it's been overwritten
        })
    })
})

But unfortunately this fails, because editor.setRootElement clears RootElement's contents.

I would really love to find a way to tap into Lexical's API, so I can test my functionality via the API without having to mirror it's exact implementation in my tests (for example defining exact expected html - it can change, doesn't mean much outside of Lexical and is a worse at describing my desired state).

Are there any options/workarounds to achieve this?


Solution

  • You can write the instance on the element or the window itself on your test environment. That's what we do for the TreeView (debugger)!

      useEffect(() => {
        const element = treeElementRef.current;
        if (element !== null) {
          element.__lexicalEditor = editor;
          return () => {
            element.__lexicalEditor = null;
          };
        }
      }, [editor]);
    

    That said, I would argue that your expectations fit a unit test more than an E2E test. An E2E test should validate the DOM, the HTML (via innerHTML) and selection (via getDOMSelection()).

    If you want to learn more about how we do E2E testing you can check Lexical's.

    ℹ️ We're planning to ship a separate testing package for users to be able to leverage all the utilities we have built on top of Playwright over the time rather than having to reimplement them on their own.