0

I am mocking navigator functions for simple clipboard functionality. Here is the relevant code:

// FUNCTION

/**
 * Adds a click event to the button which will save a string to the navigator clipboard. Checks for
 * clipboard permissions before copying.
 */

function loader(): void {
  async function copyUrl(): Promise<void> {
    const permission = await navigator.permissions.query({ name: "clipboard-write" });
    if (permission.state == "granted" || permission.state == "prompt" ) {
      await navigator.clipboard.writeText("the url");
    } else {
      console.error('Permission not supported');
    }
  }

  const button = document.querySelector('button') as HTMLElement;
  button.addEventListener('click', async () => {
    await copyUrl();
  });
}

// TEST

it('works', () => {
  // mock navigator functions
  Object.assign(navigator, {
    permissions: { 
      query: jest.fn(async () => ({ state: "granted" }))
    },
    clipboard: { 
      writeText: jest.fn(async () => {})
    }
  });

  // initialize DOM
  document.body.innerHTML = '<button></button>';
  loader();    // adds the event listener

  // click the button!
  const button = document.querySelector('button') as HTMLElement;
  button.click();

  expect(navigator.permissions.query).toHaveBeenCalledTimes(1);
  expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url');
});

The test fails on expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url') with:

Expected: "the url" Number of calls: 0

Defeats the purpose of permissions, yes, but for the sake of debugging: Try adding a clipboard call before permissions call like so?

// FUNCTION

// ...
async function copyUrl(): Promise<void> {
  
  // add this
  await navigator.clipboard.writeText('the url');

  // keep the rest still
  const permission = await navigator.permissions.query({ name: "clipboard-write" });
  // ...

}

This fails on the first assertion now, expect(navigator.permissions.query).toHaveBeenCalledTimes(1) with

Expected number of calls: 1 Received number of calls: 0

With the addition above, I also changed the assertions to be:

expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url');
expect(navigator.clipboard.writeText).toHaveBeenCalledTimes(2);
expect(navigator.permissions.query).toHaveBeenCalledTimes(1);

... which failed on the second assertion because it expected 2 calls but only received 1.

I have been testing in a VSCode devcontainer and tried out the extension firsttris.vscode-jest-runner to debug the test. With breakpoints in the loader function, I'm able to see that every single line executes perfectly with my mockup but still fails at the end of debug.

I even changed the mock navigator.permissions.query function to return { state: 'denied' } instead. Both running and debugging, it did not satisfy the permission check and gave an error to the console as expected but the test still failed at expect(navigator.permissions.query).toHaveBeenCalledTimes(1) (with the added writeText call before it).

It seems to me that after the first call of a mock function, the others just don't work.

Am I missing something? Send help pls lol

EDITS

Using jest.spyOn as in this answer has the same issues.

Using an async test with an expect.assertions(n) assertion still produces the exact same issue.

twiz
  • 23
  • 1
  • 5
  • Have a look at [this answer](https://stackoverflow.com/a/62356286/2902063) maybe you'll find some inspiration there. I expect the issue is with async and your test not being async. – alextrastero May 10 '21 at 06:37
  • @alextrastero I've already looked at that answer extensively. Same issue. Tried in an async test as well before posting with `expect.assertions` and it's the same issue. The loader is synchronous anyways and promises are all awaited. – twiz May 10 '21 at 07:25

0 Answers0