Home > Software design >  No text displays after simulating typing change on textarea in Jest/Testing-library
No text displays after simulating typing change on textarea in Jest/Testing-library

Time:09-10

I am testing a component that has a textarea element. I grab the element using screen.getByRole('textbox') and attempt to type into it using await userEvent.type(textbox, loremText) where loremText is a variable holding text.

The Jest test passes, but the text is never entered into the textarea element. Why is this happening?

Here is the test:

import { screen, render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import TextEditor from '../components/textEditor'

const loremText =
    'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum'

const renderTextEditor = (content, setContent, autoFocus) => {
    return render(
        <TextEditor
            content={content}
            setContent={setContent}
            autoFocus={autoFocus}
        />
    )
}

test('type text into textbox', async () => {
    renderTextEditor('content', jest.fn(), true)
    const textbox = screen.getByRole('textbox')
    await userEvent.type(textbox, loremText)
    screen.debug(textbox)
})

Here is the Component that has the textarea element:

import { useState } from 'react'
import MarkdownIt from 'markdown-it'

type Props = {
    content: string
    setContent: (value: string) => void
    autoFocus?: boolean
}

enum TextStyle {
    Bold,
    Italic,
    Strike,
    Code,
    Quote,
    List,
}

const markdown = new MarkdownIt({
    breaks: true,
    html: false,
    linkify: true,
})

const TextEditor = ({ content, setContent, autoFocus }: Props) => {
    const [isPreview, setIsPreview] = useState<boolean>(false)
    const [contentHtml, setContentHtml] = useState<string>(
        markdown.render(content)
    )

    const handleContentInput = (event: any) => {
        setContent(event.target.value)
        setContentHtml(markdown.render(event.target.value))
    }

    const addListItem = (
        previousText: string,
        text: string,
        start: number,
        end: number
    ): string => {
        const listIndex = text.indexOf('\n')

        if (end === 0) {
            return previousText   text
        }
        if (listIndex === -1 || listIndex > end) {
            return previousText   '* '   text
        }
        if (listIndex < start) {
            return addListItem(
                previousText   text.substring(0, listIndex   1),
                text.substring(listIndex   1),
                Math.max(start - listIndex - 1, 0),
                Math.max(end - listIndex - 1, 0)
            )
        }
        return addListItem(
            previousText   '* '   text.substring(0, listIndex   1),
            text.substring(listIndex   1),
            Math.max(start - listIndex - 1, 0),
            Math.max(end - listIndex - 1, 0)
        )
    }

    const addStyle = (style: TextStyle) => {
        const sel = document.getElementById(
            'inputTextArea'
        ) as HTMLTextAreaElement
        const start = sel.selectionStart
        const end = sel.selectionEnd

        let newContent: string = content
        if (style === TextStyle.Italic) {
            newContent =
                content.substring(0, start)  
                '*'  
                content.substring(start, end)  
                '*'  
                content.substring(end)
        } else if (style === TextStyle.Bold) {
            newContent =
                content.substring(0, start)  
                '**'  
                content.substring(start, end)  
                '**'  
                content.substring(end)
        } else if (style === TextStyle.Strike) {
            newContent =
                content.substring(0, start)  
                '~~'  
                content.substring(start, end)  
                '~~'  
                content.substring(end)
        } else if (style === TextStyle.Code) {
            newContent =
                content.substring(0, start)  
                '`'  
                content.substring(start, end)  
                '`'  
                content.substring(end)
        } else if (style === TextStyle.Quote) {
            newContent =
                content.substring(0, start)  
                '> '  
                content.substring(start, end)  
                '\n'  
                content.substring(end)
        } else if (style === TextStyle.List) {
            newContent = addListItem('', content, start, end)
        }
        setContent(newContent)
        setContentHtml(markdown.render(newContent))
    }

    const insertImage = () => {
        const sel = document.getElementById(
            'inputTextArea'
        ) as HTMLTextAreaElement
        const start = sel.selectionStart
        const end = sel.selectionEnd
        const newContent =
            content.substring(0, start)  
            `![${content.substring(
                start,
                end
            )}](https://paste-image-url-here.jpg|png|svg|gif)`  
            content.substring(end)
        setContent(newContent)
        setContentHtml(markdown.render(newContent))
    }

    const insertLink = () => {
        const sel = document.getElementById(
            'inputTextArea'
        ) as HTMLTextAreaElement
        const start = sel.selectionStart
        const end = sel.selectionEnd
        const newContent =
            content.substring(0, start)  
            `[${content.substring(start, end)}](https://)`  
            content.substring(end)
        setContent(newContent)
        setContentHtml(markdown.render(newContent))
    }

    return (
        <div>
            <div>
                <div className="buttons">
                    <div className="group basic">
                        <button onClick={() => addStyle(TextStyle.Bold)}>
                            <img
                                src={require('../../public/images/bold.svg')}
                            />
                        </button>
                        <button onClick={() => addStyle(TextStyle.Italic)}>
                            <img
                                src={require('../../public/images/italic.svg')}
                            />
                        </button>
                        <button onClick={() => addStyle(TextStyle.Strike)}>
                            <img
                                src={require('../../public/images/strike.svg')}
                            />
                        </button>
                        <button onClick={() => addStyle(TextStyle.Code)}>
                            <img
                                src={require('../../public/images/codeblock.svg')}
                            />
                        </button>
                        <button onClick={() => addStyle(TextStyle.Quote)}>
                            <img
                                src={require('../../public/images/quote.svg')}
                            />
                        </button>
                        <button onClick={() => addStyle(TextStyle.List)}>
                            <img
                                src={require('../../public/images/bullet.svg')}
                            />
                        </button>
                        <button onClick={insertLink}>
                            <img
                                src={require('../../public/images/link.svg')}
                            />
                        </button>
                        <button onClick={insertImage}>
                            <img src={require('../../public/images/img.svg')} />
                        </button>
                    </div>
                    <div className="group">
                        <button
                            onClick={() =>
                                window.open(
                                    'https://commonmark.org/help/',
                                    '__blank'
                                )
                            }
                        >
                            <img
                                src={require('../../public/images/info_small.svg')}
                            />
                        </button>
                        <button
                            className="button-border"
                            onClick={() => setIsPreview(!isPreview)}
                        >
                            {isPreview ? 'Edit' : 'Preview'}
                        </button>
                    </div>
                </div>
                {isPreview && (
                    <div className="block-content preview-box">
                        <div className="content">
                            <div
                                dangerouslySetInnerHTML={{
                                    __html: contentHtml,
                                }}
                            />
                        </div>
                    </div>
                )}
                {!isPreview && (
                    <textarea
                        id="inputTextArea"
                        onChange={handleContentInput}
                        value={content ?? ''}
                        autoFocus={autoFocus}
                    />
                )}
            </div>
        </div>
    )
}

export default TextEditor

When I use screen.debug(textbox) to see if the text was entered the terminal out is:

<textarea
      id="inputTextArea"
    >
      content
    </textarea>

CodePudding user response:

You are using the content prop of TextEditor to render the content of the textarea. As you only put there a static value, "content", this value will override the content of the textarea.

You can use a wrapper element to implement the content and setContent props:

const TestWrapper = () => {
  const [content, setContent] = useState("");
  return <TextEditor
            content={content}
            setContent={setContent}
            autoFocus={true}
          />
}

const renderTextEditor = () => {
    return render(
        <TestWrapper />
    )
}

test('type text into textbox', async () => {
    renderTextEditor();
    const textbox = screen.getByRole('textbox')
    userEvent.type(textbox, loremText) // userEvent.type is not an async function
    await waitFor(() => {
        expect(textbox).toHaveValue(loremText);
    });
})
  • Related