-2

I have a toast component that spawns success and error toast on clicking on the success and error buttons respectively.

Demo - Link

I have written a test using jest and @testing-library/dom -

import '@testing-library/jest-dom';
import { screen } from '@testing-library/dom';
import { getExampleDom } from '../test-utils';

test.only('\`n\` toasts appear on \`n\` button clicks', async () => {
  const { user } = getExampleDom();
  const succesBtn = screen.getByRole("button", { name: /Trigger success toast/i });

  for(let i = 0; i < 3; i++) {
    user.click(succesBtn);
  }
  
  const successToasts = await screen.findAllByRole("alert");
  expect(successToasts).toHaveLength(3);
});

test-utils.js

import userEvent from '@testing-library/user-event';
import Toast from "./Toast";
import { getEl } from "./utils";

export function getExampleDom() {
  document.body.innerHTML = `
    <div id="controller">
      <button id="successBtn">Trigger Success Toast</button>
      <button id="errorBtn">Trigger Error Toast</button>
    </div>
  `;
/**
 * Test behaves unexpectedly after requiring index file
 * -> First test passes
 * -> Subsequent test fails
 * -> TODO: need to mock style, comment style import from index.js meanwhile. 
 */
//require("./index");

/** require("../index"); not working correctly, writing js logic manually below*/
const toast = new Toast({ closable: true });

getEl('#successBtn').addEventListener('click', function handleSuccessBtnClick(e) {
  toast.success('Spawned a success toast');
});

getEl('#errorBtn').addEventListener('click', function handleErrorBtnClick(e) {
  toast.error('Spawned an error toast');
});

return { user: userEvent.setup() };
}

test fails

 ● `n` toasts appear on `n` button clicks

    expect(received).toHaveLength(expected)

    Expected length: 3
    Received length: 1
    Received array:  [<div aria-label="Spawned a success toast" class="toast toast__success" data-id="0" role="alert">Spawned a success toast<span class="toast-dismiss" data-close-id="0">x</span></div>]

      24 |   
      25 |   const successToasts = await screen.findAllByRole("alert");
    > 26 |   expect(successToasts).toHaveLength(3);
         |                         ^
      27 | });
      28 |
      29 | test('Display error toast on `Trigger Error Toast` button click', async () => {

Button is clicked 3 times using for loop, but received array has only one toast div in it.

Stack
  • 429
  • 1
  • 4
  • 12

1 Answers1

0

My conclusion that not all toasts are appeared, when you call expect, so you need to wait for them.

Try to use waitFor

import { screen, waitFor } from '@testing-library/dom';

...

await waitFor(async () =>
    expect(await screen.findAllByRole("alert")).toHaveLength(3),
  )
Yaroslavm
  • 1,762
  • 2
  • 7
  • 15
  • Test passed! But, it is also passing for toHaveLength(1) and toHaveLength(2) – Stack Aug 02 '23 at 15:06
  • @Stack yep, cause your elements are appeared async. And it is valid, as far as after click you have small delay, when toast should appear. I guess it wouldn't pass for toHaveLength(4), right? Your point is to wait, when toasts amount would equal 3. – Yaroslavm Aug 02 '23 at 15:36
  • But we are waiting for alerts to appear using waitFor. So, our assertion should run only after all the alerts are present in the dom, it should pass for length 3 only – Stack Aug 02 '23 at 16:01
  • It works a bit different. waitFor calls expect until it would not throw exception. so, `waitFor(() => ...toHaveLength(1))` would pass on the first state after click (2nd and 3rd aren't appeared yet) and in the time, when they would disappear after wait. `waitFor(() => ...toHaveLength(2))` would pass also, as far as it would catch the state when 1st and second are appeared and 3rd is still not appeared. `waitFor(() => ...toHaveLength(4))` wouldn't pass, in any time you would not have 3 pop-ups. – Yaroslavm Aug 02 '23 at 16:19
  • I looked into your demo - your pop-ups appeared one by one and then quickly disappeared. So, IMHO, in your test you expect that 3 clicks would trigger 3 modals. Not more, not less. – Yaroslavm Aug 02 '23 at 16:21