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);
});
})