英文:
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 "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.
答案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) => {
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
答案2
得分: 0
navigator.clipboard.readText() 返回一个promise,而不是一个string。MDN文档
这就是为什么 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('copies data to clipboard', async () => {
render(<MyComponent data={data} />);
// Reset the mock functions before each test
mockClipboard.writeText.mockReset();
mockClipboard.readText.mockReset().mockResolvedValue(formattedData);
const copyButton = screen.getByRole('button', { name: 'Copy Data' });
fireEvent.click(copyButton);
await waitFor(() => {
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(formattedData);
});
const clipboardContent = await navigator.clipboard.readText();
expect(clipboardContent).toEqual(formattedData);
expect(copyButton).toHaveTextContent('Copied!');
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论