最近,我一直在玩React + Jest,试图在外部TDD上发挥自己的技能,设置背后的想法是提供一种开始测试的简便方法。但是,该应用程序是一个简单的应用程序,它只是格式化的JSON字符串,但是它有一些有趣的内容,例如,访问剪贴板以读写。
测试时,这种事情似乎很微不足道,所以某种
测试双需要到位,事实证明确实很容易处理,但是测试代码有点冗长。
在这篇文章中,我将尝试描述我在嘲笑剪贴板API上遇到的问题。
一些上下文
通过特定的命令document.execCommand()
完成了从浏览器中进出的复制/粘贴事物。因此,正如杰森·米勒(Jason Miller)和托马斯·斯坦纳(Thomas Steiner)所描绘的那样,它具有重要的缺点:execCommand is synchronous。
从用户的角度来看,这种行为导致经验不佳,直到完成页面。因此,构建了异步API。
新的API允许开发人员创建在剪贴板上进行阅读或写作时不会阻止的应用程序。随着新的API,新的行为发挥了作用:
现在,用户有能力允许或阻止与剪贴板的交互作用 - 以前无法使用 execcommand 。
。浏览器API
这里要描绘的第一件事是浏览器必须允许从传输区域读取/写入的剪贴板API。权限会自动发生。
解决方案
开发与新剪贴板API交互的应用程序不是问题,互联网上有不同的示例,web.dev的博客文章就是其中之一。
当我们要测试将在剪贴板上读取/写入的代码时应该发生的交互时,挑战就会出现。
鉴于这种情况创建了插件jest-clipboard,它使开发人员能够设置剪贴板状态并专注于预期行为,而不是模拟剪贴板API的详细信息。
处理文字
让我们从一个方案开始,我们需要阅读用户在传输区域中具有的文本。
在这种情况下,使用嘲笑式clipboard的第一件事是将插件设置为每个挂钩功能之前和之后。这将使Jest-Clipboard能够正确设置套件中每个测试的场景:
import { setUpClipboard, tearDownClipboard } from 'jest-clipboard';
describe('My test', () => {
beforeEach(() => {
setUpClipboard();
});
afterEach(() => {
tearDownClipboard();
});
});
下一步是编写测试并开始使用剪贴板主张。例如,在剪贴板传输中设置文本的是测试如下:
import { setUpClipboard, tearDownClipboard, writeTextToClipboard, readTextFromClipboard } from 'jest-clipboard';
describe('My test', () => {
beforeEach(() => {
setUpClipboard();
});
afterEach(() => {
tearDownClipboard();
});
it('should use text from clipboard', async () => {
await writeTextToClipboard('I want this to be in the transfer area');
// in here the production code needs to be used
expect(await readTextFromClipboard()).toEqual('I want this to be in the transfer area');
});
});
我们可能面临的另一个示例是向剪贴板发送一些信息,为此,剪贴板API具有writetext功能,让我们看看它的状况:
import { setUpClipboard, tearDownClipboard, writeTextToClipboard, readTextFromClipboard } from 'jest-clipboard';
describe('My test', () => {
beforeEach(() => {
setUpClipboard();
});
afterEach(() => {
tearDownClipboard();
});
it('should use text from clipboard', async () => {
// no need to write something to the clipboard first, the setup/teardown make sure to give
// a clean state for every test.
// in here the production code needs to be used, given an example that
// the production code writes 'I want this to be in the transfer area'
// this text would be there.
// Thus, the assertion would be the same as the previous example.
expect(await readTextFromClipboard()).toEqual('I want this to be in the transfer area');
});
});
剪贴板的设计是为了支持多种类型的媒体,为此,它被分为我们刚刚看到的文本和其他内容(例如,可以图像)。
处理其他媒体类型基本上是相同的过程,让我们看看下一节的情况。
处理其他事情
剪贴板的设置保持不变,不需要更改,需要更改的部分是我们调用剪贴板写和读取的方法。
让我们以相同的示例来撰写剪贴板中的文本/读取文本,然后将其转换为使用剪贴板中的读/写API的示例。
要注意的第一件事是,现在,我们开始处理ClipboardItems和Blobs而不是原始字符串:
describe('My test', () => {
beforeEach(() => {
setUpClipboard();
});
afterEach(() => {
tearDownClipboard();
});
it('write to clipboard (write)', async () => {
// this is now a blob and not a text anymore
// here we need to specify the type of media
const blob = new Blob(['my text'], { type: 'text/plain' });
const clipboardItem: ClipboardItem = {
presentationStyle: 'inline',
types: ['plain/text'],
getType(type: string): Promise<Blob> {
return new Promise((resolve) => {
resolve(blob)
});
}
};
const clipboardItems: ClipboardItems = [clipboardItem]
await writeItemsToClipboard(clipboardItems);
const items = await readFromClipboard();
const type1 = await items[0].getType(imagePng);
expect(await type1.text()).toBe('my text');
});
});
让我们假装我们要处理用户将从浏览器复制并粘贴到我们的应用程序
的图像
(就像Google文档一样或概念一样)。
这样做之前有几个步骤,让我们枚举它们:
- 我们需要一个图像作为示例
- 我们将将图像转换为斑点
- 主张文件内容或其他信息
从剪贴板中读取另一种媒体类型将如下:
describe('My test', () => {
beforeEach(() => {
setUpClipboard();
});
afterEach(() => {
tearDownClipboard();
});
it('write to clipboard (write)', async () => {
// this is the first items, we are creating an image from a base64 string
// and we are also creating a blob from that
const imagePng = 'image/png';
const base64Image ='iVBORw0KGgoAAAANSUhEUgAAAEYAAABACAIAAAAoFZbOAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAaElEQVRoge3PQQ3AIADAQMAQD4J/azOxZOlyp6Cd+9zxL+vrgPdZKrBUYKnAUoGlAksFlgosFVgqsFRgqcBSgaUCSwWWCiwVWCqwVGCpwFKBpQJLBZYKLBVYKrBUYKnAUoGlAksFlgoeg2ABFxfCv1QAAAAASUVORK5CYII='
const buffer = Buffer.from(base64Image, 'base64');
const blob = new Blob([buffer]);
// refers to https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/clipboard-apis/async-promise-write-blobs-read-blobs.https.html
const clipboardItem: ClipboardItem = {
presentationStyle: 'inline',
types: [imagePng],
getType(type: string): Promise<Blob> {
return new Promise((resolve) => {
resolve(blob)
});
}
};
// finally we can grab what the clipboard has
const clipboardItems: ClipboardItems = [clipboardItem]
await writeItemsToClipboard(clipboardItems);
const items = await readFromClipboard();
const type1 = await items[0].getType(imagePng);
// the assertion here is with the size as the text would give a bibary encoded
expect(type1.size).toBe(182);
});
});
剪贴板API根据BLOB接口和剪贴板项目处理不同的媒体类型,它使开发人员有能力在粘贴时决定从用户那里取出哪些东西。
例如,在剪贴板中检测到图像时,开发人员可以触发图像,同样使用视频内容。
要点
剪贴板API背后的主要思想是使开发人员通过异步剪贴板处理来改善用户体验。
剪贴板API提供要使用的读取,写入和权限接口。首先,用户需要允许其使用,然后开发人员可以在原始文本和其他媒体类型(例如图像)之间微调剪贴板处理。
使用此功能强大的API,它的测试方面被遗留在没有任何支持的情况下,以帮助开发人员在首次编写测试时测试其应用程序。
作为JEST是JavaScript生态系统中最受欢迎的测试跑步者之一,Jest-Clipboard为开发人员提供了专注于应用程序行为而不是模拟剪贴板API的API。
jest-clipboard遵循剪贴板API中使用的转换,并添加了显示意图的可读API。例如,WriteText将在剪贴板上写文本,而读取将从剪贴板中读取文本。它还提供了设置和清洁测试状态的实用程序,以避免行为使测试变得艰难。