I’m building a twitter clone app and I’m utilizing TDD. I’ve gotten to the point where I’m trying to test a user being able to edit a tweet. The idea is, if a user clicks on the current tweet, it will display the input again so that you can edit the current tweet as you like. You then click the tweet button again, and it sets the tweet.
I’ve tested manually in Storybook, and it works as I expect it to, however, I’m unable to get my jest test to pass. Jest prints the DOM at test failure, and it does still show that ‘My First Tweet’ is in the DOM. My guess is that this is a timng issue, but I’m not sure how to fix it. I’ve tried act
& waitFor
but neither of those have worked. What am I doing wrong?
Rendering my component
beforeEach(() => {
renderResult = render(
<Tweet profilePic={imageUrl} username="ndland" title="Tweet Title" />,
);
});
Testing editing a tweet
describe("editing a tweet", () => {
const user = userEvent.setup();
let input: Element;
beforeEach(async () => {
input = screen.getByPlaceholderText("What's happening?");
const button = screen.getByRole("button", { name: "Tweet" });
await act(async () => {
await user.click(input);
await user.keyboard("My First Tweet");
await user.click(button);
});
});
it("should allow a user to edit their tweet", async () => {
const twxxt = screen.getByText("My First Tweet");
expect(twxxt).toBeInTheDocument();
expect(screen.queryByText("My edited Tweet")).not.toBeInTheDocument();
await user.click(twxxt);
expect(input).toHaveValue("My First Tweet");
await user.keyboard("My edited Tweet");
await user.click(screen.getByRole("button", { name: "Tweet" }));
await waitFor(() => expect(input).toHaveValue("My edited Tweet"));
});
});
Component in question
const Tweet: React.FC<TweetProps> = ({ profilePic, username, title }) => {
const [input, setInput] = React.useState<string | undefined>(undefined);
const [tweet, setTweet] = React.useState<string | undefined>(undefined);
const [placeholder, setPlaceholder] = React.useState<string | undefined>(
"What's happening?",
);
const [placeholderColor, setPlaceholderColor] = React.useState<string>(
"placeholder:text-slate-400",
);
const [likes, setLikes] = React.useState<number>(0);
const [hasLiked, setHasLiked] = React.useState<boolean>(false);
const handleTweet = () => {
if (!input) {
setPlaceholder("Enter a twxxt");
setPlaceholderColor("placeholder:text-red-500");
}
setTweet(input);
};
const handleLike = () => {
if (hasLiked) {
setLikes(likes - 1);
setHasLiked(false);
return;
}
if (input && !hasLiked) {
setLikes(likes + 1);
setHasLiked(true);
}
};
const handleTwxxtClick = () => {
setPlaceholder(tweet);
setTweet(undefined);
};
return (
<div className="dark:bg-slate-800 mx-auto m-4 max-w-md overflow-hidden rounded-xl bg-white shadow-md md:max-w-2xl p-4 flex flex-row space-x-8">
<div className="relative w-16 h-16 flex-shrink-0">
<div>
<Image
className="rounded-full h-full w-full object-cover absolute"
src={profilePic}
alt="Profile Picture"
width={100} // note: see https://nextjs.org/docs/app/building-your-application/optimizing/images#remote-images for more info
height={100}
/>
</div>
</div>
<div className="flex-grow">
<div
aria-label="username"
className="text-indigo-500 font-semibold md:text-lg py-2"
>
@{username}
</div>
<div aria-label="title" className="dark:text-white font-semibold">
{title}
</div>
{tweet ? (
<>
<div
onClick={handleTwxxtClick}
className="dark:text-white py-4 hover:cursor-pointer"
>
{tweet}
</div>
<div className="flex justify-normal space-x-2 items-center">
{likes === 0 ? (
<FontAwesomeIcon
aria-label="like"
onClick={handleLike}
className="text-white"
size="xl"
icon={outlineThumb}
/>
) : (
<FontAwesomeIcon
aria-label="like"
onClick={handleLike}
className="text-white"
size="xl"
icon={solidThumb}
/>
)}
{likes !== 0 && (
<div aria-label="likes" className="text-gray-400">
Likes: {likes}
</div>
)}
</div>
</>
) : (
<>
<input
type="text"
placeholder={placeholder}
value={tweet}
onChange={(e) => setInput(e.target.value)}
className={`w-full my-2 p-2 rounded-md dark:bg-slate-800 dark:text-white dark:border-2 dark:border-indigo-800 ${placeholderColor}`}
/>
<div className="flex justify-end items-center">
<button
onClick={handleTweet}
className="bg-indigo-500 text-white rounded-full px-4 py-2"
>
Tweet
</button>
</div>
</>
)}
</div>
</div>
);
};
export default Tweet;
edit forgot to add the test failure.
● Tweet › editing a tweet › should allow a user to edit their tweet
expect(element).toHaveValue(My edited Tweet)
Expected the element to have value:
My edited Tweet
Received:
My First Tweet
edit 2: I see the following output when running my tests:
<input
aria-label="tweet input"
class="w-full my-2 p-2 rounded-md dark:bg-slate-800 dark:text-white dark:border-2 dark:border-indigo-800 placeholder:text-slate-400"
placeholder="My First Tweet"
type="text"
value=""
/>
Which tells me that it’s hitting the handler, but when I call user.keyboard
it’s not sending those keys to the input
. I have also added the line @Willow suggested, so input
should now have the focus.
2
Answers
Ok, it seems that the issue was, I was referencing the wrong object for my assertions. The
input
object I was referencing, was still holding the original value 'My First Tweet'. Below is the modified test, which is passing as expected:In this example, I've just pulled the
screen.getBy...
into a helper function, so I can reuse the code similarly, but now, when I need to update the object, I can do so easily. A rather simple solution to a simple problem, just an oversight by me. Hope this helps.As alluded to in my comment, try this: