使用ChakraUI中的`useClipboard`钩子时,如何测试粘贴内容。

huangapple go评论82阅读模式
英文:

How to test pasted content when using `useClipboard` hook in ChakraUI

问题

I have a component that has a copy button that copies some data to the clipboard.

I would like to test that the correct data has been copied to the clipboard using jest & @testing-library/react

This is the component's implementation:

import React from "react";
import { Button, useClipboard } from "@chakra-ui/react";
import { formatData } from "@/lib/formatters";

export const MyComponent = ({ data }: Data) => {
  const { hasCopied, onCopy } = useClipboard(formatData(data));
  return <Button onClick={onCopy}>{hasCopied ? "Copied!" : "Copy Data"}</Button>;
};

And here is the unit test:

it("copies data to clipboard", () => {
  render(<MyComponent data={data} />);
  screen.getByRole("button").click();
  fireEvent.click(screen.getByRole("button", { name: "Copy Data" }));
  // Expect that the clipboard data is set to the formatted data
  expect(navigator.clipboard.readText()).toEqual(formatData(data)); // Doesn't work!
});

However, when I run the unit test, I get an error:

TypeError: Cannot read properties of undefined (reading 'readText')

Is there a way to elegantly test the pasted content?

PS: useClipboard is using copy-to-clipboard package under the hood, which could be mocked, but that solution wouldn't be so elegant.

英文:

I have a component that has a copy button that copies some data to the clipboard.

I would like to test that the correct data has been copied to the clipboard using jest & @testing-library/react

This is the component's implementation:

import React from &quot;react&quot;;
import { Button, useClipboard } from &quot;@chakra-ui/react&quot;;
import { formatData } from &quot;@/lib/formatters&quot;;

export const MyComponent = ({ data }: Data) =&gt; {
  const { hasCopied, onCopy } = useClipboard(formatData(data));
  return &lt;Button onClick={onCopy}&gt;{hasCopied ? &quot;Copied!&quot; : &quot;Copy Data&quot;}&lt;/Button&gt;;
};

And here is the unit test

it(&quot;copies data to clipboard&quot;, () =&gt; {
  render(&lt;MyComponent data={data} /&gt;);
  screen.getByRole(&quot;button&quot;).click();
  fireEvent.click(screen.getByRole(&quot;button&quot;, { name: &quot;Copy Data&quot; }));
  // Expect that the clipboard data is set to the formatted data
  expect(navigator.clipboard.readText()).toEqual(formatData(data)); // Doesn&#39;t work!
});

However, when I run the unit test, I get an error
TypeError: Cannot read properties of undefined (reading &#39;readText&#39;)

Is there a way to elegantly test the pasted content?

PS: useClipboard is using copy-to-clipboard package under the hood, which could be mocked, but that solution wouldn't be so elegant.

答案1

得分: 2

以下是您要翻译的内容:

Jest is running the tests with jsdom and jsdom doesn't support navigator.clipborad, this is why clipboard is undefined and cannot read property writeText of undefined. However react testing library replaces window.navigator.clipboard with a stub when userEvent.setup() is used.

如果您的实现是使用 navigator.clipboard 而不是 copy-to-clipboard

const MyComponent = ({ data }: Data) => {
  const [hasCopied, setHasCopied] = React.useState(false)
  const val = formatData(data)

  return (
    <Button onClick={() => {
      navigator.clipboard.writeText(val).then(() => {
        setHasCopied(true)
      });
    }}>{hasCopied ? "Copied!" : "Copy Data"}</Button>
  );
};

you would have been able to expect:

test("should return text, reading the clipboard text", async () => {

  const user = userEvent.setup()

  render(<App />)
  const linkElement = screen.getByText(/copy data/i)
  user.click(linkElement)

  await waitFor(() => {
    expect(linkElement).toHaveTextContent('Copied!')
  })

  await expect(navigator.clipboard.readText()).resolves.toEqual("{\"hello\":\"world\"}")
})

but since execCommand is used instead of the navigator.clipboard API it would have to be mocked as well, as it's not supported by jsdom

to mock it I would use the already prepared stub by react testing library like this


let user: ReturnType<typeof userEvent.setup>;

beforeAll(() => {
  user = userEvent.setup();

  Object.assign(document, {
    execCommand: (cmd: string) => {
      switch (cmd) {
        case "copy":
          user.copy();
          return true;
        case "paste":
          user.paste();
          return;
      }
    },
  });
});

and the above test should work once again

英文:

Jest is running the tests with jsdom and jsdom doesn't support navigator.clipborad, this is why clipboard is undefined and cannot read property writeText of undefined. However react testing library replaces window.navigator.clipboard with a stub when userEvent.setup() is used.

If your implementation was using navigator.clipboard instead of copy-to-clipboard

const MyComponent = ({ data }: Data) =&gt; {
  const [hasCopied, setHasCopied] = React.useState(false)
  const val = formatData(data)

  return (
    &lt;Button onClick={() =&gt; {
      navigator.clipboard.writeText(val).then(() =&gt; {
        setHasCopied(true)
      });
    }}&gt;{hasCopied ? &quot;Copied!&quot; : &quot;Copy Data&quot;}&lt;/Button&gt;
  );
};

you would have been able to expect:

test(&quot;should return text, reading the clipboard text&quot;, async () =&gt; {

  const user = userEvent.setup()

  render(&lt;App /&gt;)
  const linkElement = screen.getByText(/copy data/i)
  user.click(linkElement)

  await waitFor(() =&gt; {
    expect(linkElement).toHaveTextContent(&#39;Copied!&#39;)
  })

  await expect(navigator.clipboard.readText()).resolves.toEqual(&quot;{\&quot;hello\&quot;:\&quot;world\&quot;}&quot;)
})

but since execCommand is used instead of the navigator.clipboard API it would have to be mocked as well, as it's not supported by jsdom

to mock it I would use the already prepared stub by react testing library like this


let user: ReturnType&lt;typeof userEvent.setup&gt;;

beforeAll(() =&gt; {
  user = userEvent.setup();

  Object.assign(document, {
    execCommand: (cmd: string) =&gt; {
      switch (cmd) {
        case &quot;copy&quot;:
          user.copy();
          return true;
        case &quot;paste&quot;:
          user.paste();
          return;
      }
    },
  });
});

and the above test should work once again

答案2

得分: 0

navigator.clipboard.readText() 返回一个promise,而不是一个stringMDN文档

这就是为什么 expect(navigator.clipboard.readText()).toEqual(formatData(data)) 不起作用。

英文:

navigator.clipboard.readText() returns a promise, not a string. MDN-Docs

That's why expect(navigator.clipboard.readText()).toEqual(formatData(data)) isn't working.

// MOCK CLIPBOARD

const mockClipboard = {
  writeText: jest.fn(),
  readText: jest.fn(),
};
global.navigator.clipboard = mockClipboard;

it(&#39;copies data to clipboard&#39;, async () =&gt; {
  render(&lt;MyComponent data={data} /&gt;);
 // Reset the mock functions before each test
  mockClipboard.writeText.mockReset();
 mockClipboard.readText.mockReset().mockResolvedValue(formattedData);

  const copyButton = screen.getByRole(&#39;button&#39;, { name: &#39;Copy Data&#39; });
  fireEvent.click(copyButton);
  await waitFor(() =&gt; {
  expect(navigator.clipboard.writeText).toHaveBeenCalledWith(formattedData);
  });
  const clipboardContent = await navigator.clipboard.readText();
  expect(clipboardContent).toEqual(formattedData);
  expect(copyButton).toHaveTextContent(&#39;Copied!&#39;);
});

huangapple
  • 本文由 发表于 2023年7月6日 18:26:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76627868.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定