初学react实现路由跳转

Building web applications is not an easy task, as of today. To do so, you’re probably using something like React, Vue, or Angular. Your app is faster, the code is both more maintainable and more readable. But that’s not enough. The more your codebase grows, the more complex and buggy it is. So if you care about that, learn to write tests. That’s what we’ll do today for React apps.

到目前为止,构建Web应用程序并非易事。 为此,您可能正在使用React,Vue或Angular之类的工具。 您的应用程序速度更快,代码更易于维护和可读。 但这还不够。 您的代码库增长的越多,它的复杂性和漏洞就越多。 因此,如果您对此有所关注,请学习编写测试。 这就是我们今天要为React应用程序所做的。

Luckily for you, there are already testing solutions for React, especially one: react-testing-library made by Kent C. Dodds. So, let’s discover it, shall we?

幸运的是,已经有针对React的测试解决方案,尤其是其中一种: Kent C. Dodds制作的react-testing-library 。 那么,让我们发现它吧?

为什么要使用React测试库? (Why React Testing Library?)

Basically, React Testing Library (RTL) is made of simple and complete React DOM testing utilities that encourage good testing practices, especially one:

基本上,React测试库(RTL)由简单而完整的React DOM测试实用程序组成,这些实用程序鼓励良好的测试实践,尤其是以下一种:

“The more your tests resemble the way your software is used, the more confidence they can give you. ”— Kent C. Dodds

“测试越类似于软件使用方式,它们给您的信心就越大。 ”- 肯特·多德斯 ( Kent C. Dodds)

In fact, developers tend to test what we call implementation details. Let’s take a simple example to explain it. We want to create a counter that we can both increment and decrement. Here is the implementation (with a class component) with two tests: The first one is written with Enzyme and the other one with React Testing Library.

实际上,开发人员倾向于测试我们所谓的实现细节 。 让我们举一个简单的例子来解释它。 我们想创建一个既可以递增也可以递减的计数器。 这是带有两个测试的实现(带有类组件):第一个是用Enzyme编写的,另一个是用React Testing Library编写的。

import React from "react";
import { shallow } from "enzyme";import Counter from "./counter";describe("<Counter />", () => {it("properly increments and decrements the counter", () => {const wrapper = shallow(<Counter />);expect(wrapper.state("count")).toBe(0);wrapper.instance().increment();expect(wrapper.state("count")).toBe(1);wrapper.instance().decrement();expect(wrapper.state("count")).toBe(0);});
});

Note: Don’t worry if you don’t fully understand the test files because we’ll see all of this afterward.

注意:如果您不完全了解测试文件,请不要担心,因为我们稍后会看到所有这些信息。

Can you guess which test file is the best one and why? If you’re not used to tests, you may think that both are fine. In fact, the two tests make sure that the counter is incremented and decremented. However, the first one is testing implementation details and it has two risks:

您能猜出哪个测试文件是最好的,为什么? 如果您不习惯测试,您可能会认为两者都很好。 实际上,这两个测试可确保计数器递增和递减。 但是,第一个是测试实现细节,它有两个风险:

  • false positive: The test passes even if the code is broken.

    误报:即使代码损坏,测试也会通过。
  • false negative: The test is broken even if the code is right.

    假阴性:即使代码正确,测试也会失败。

假阳性 (False positive)

Let’s say we want to refactor our components because we want to make it possible to set any count value. So we remove our increment and decrement methods and then add a new setCount method. We forgot to wire this new method to our different buttons:

假设我们要重构组件,因为我们希望可以设置任何计数值。 因此,我们删除了incrementdecrement方法,然后添加了一个新的setCount方法。 我们忘记了将此新方法连接到我们的不同按钮:

class Counter extends React.Component {// ...setCount = (count) => this.setState({ count })render() {return (<div><button onClick={this.decrement}>-</button><p>{this.state.count}</p><button onClick={this.increment}>+</button></div>)}
}

The first test (Enzyme) will pass, but the second one (RTL) will fail. Indeed, the first one doesn’t care if our buttons are correctly wired to the methods. It just looks at the implementation itself: our increment and decrement method. This is a false positive.

第一个测试(酶)将通过,但第二个测试(RTL)将失败。 确实,第一个并不关心我们的按钮是否正确连接到方法。 它只是看实现本身:我们的incrementdecrement方法。 这是一个误报。

假阴性 (False negative)

Now, what if we wanted to refactor our class component to hooks? We would change its implementation:

现在,如果我们想将类组件重构为钩子呢? 我们将更改其实现:

import React, { useState } from "react"
const Counter = () => {const [count, setCount] = useState(0)const increment = () => setCount((count) => count + 1)const decrement = () => setCount((count) => count - 1)return (<div><button onClick={decrement}>-</button><p>{count}</p><button onClick={increment}>+</button></div>)
}
export default Counter

This time, the first test is going to be broken even if your counter still works. This is a false negative. Enzyme will complain about state not being able to work on functional components:

这次,即使您的计数器仍然可以工作,第一个测试也将被破坏。 这是一个假阴性。 酶会抱怨state无法在功能组件上起作用:

ShallowWrapper::state() can only be called on class components

Then we have to change the test:

然后我们必须更改测试:

import React from "react"
import { shallow } from "enzyme"
import Counter from "./counter"
describe("<Counter />", () => {it("properly increments and decrements the counter", () => {const setValue = jest.fn()const useStateSpy = jest.spyOn(React, "useState")useStateSpy.mockImplementation((initialValue) => [initialValue, setValue])const wrapper = shallow(<Counter />)wrapper.find("button").last().props().onClick()expect(setValue).toHaveBeenCalledWith(1)// We can't make any assumptions here on the real count displayed// In fact, the setCount setter is mocked!wrapper.find("button").first().props().onClick()expect(setValue).toHaveBeenCalledWith(-1)})
})

To be honest, I’m not even sure if this is the right way to test it with Enzyme when it comes to hooks. In fact, we can’t even make assumptions about the displayed count because of the mocked setter.

老实说,我什至不确定这是否是在钩子上用酶测试它的正确方法。 实际上,由于设置方法被嘲笑,我们甚至无法对显示的计数做出假设。

However, the test without implementation details works as expected in all cases. So, if we had something to retain so far, it would be to avoid testing implementation details.

但是,没有实现细节的测试在所有情况下都可以正常工作。 因此,如果到目前为止我们要保留一些东西,那就是避免测试实现细节。

Note: I’m not saying Enzyme is bad. I’m just saying testing implementation details will make tests harder to maintain and unreliable. In this article, we are going to use React Testing Library because it encourages testing best practices.

注意:我并不是说酵素不好。 我只是说测试实现细节将使测试难以维护且不可靠。 在本文中,我们将使用React Testing库,因为它鼓励测试最佳实践。

简单的测试步骤 (A Simple Test Step-by-Step)

Maybe there is still an air of mystery around the test written with React Testing Library. As a reminder, here it is:

用React Testing Library编写的测试也许仍然充满神秘感。 提醒一下,这里是:

import React from "react"
import { fireEvent, render, screen } from "@testing-library/react"
import Counter from "./counter"
describe("<Counter />", () => {it("properly increments and decrements the counter", () => {render(<Counter />)const counter = screen.getByText("0")const incrementButton = screen.getByText("+")const decrementButton = screen.getByText("-")fireEvent.click(incrementButton)expect(counter.textContent).toEqual("1")fireEvent.click(decrementButton)expect(counter.textContent).toEqual("0")})
})

Let’s decompose it to understand how it’s made. Introducing the AAA pattern: Arrange, Act, Assert.

让我们分解一下以了解其制作方法。 引入AAA模式:安排,执行,声明。

import React from "react"
import { fireEvent, render, screen } from "@testing-library/react"
import Counter from "./counter"
describe("<Counter />", () => {it("properly increments and decrements the counter", () => {// Arrangerender(<Counter />)const counter = screen.getByText("0")const incrementButton = screen.getByText("+")const decrementButton = screen.getByText("-")// ActfireEvent.click(incrementButton)// Assertexpect(counter.textContent).toEqual("1")// ActfireEvent.click(decrementButton)// Assertexpect(counter.textContent).toEqual("0")})
})

Almost all of your tests will be written that way:

几乎所有测试都将以这种方式编写:

  1. You arrange (= set up) your code so that everything is ready for the next steps.

    安排 (=设置)您的代码,以便为下一步做好一切准备。

  2. You act, that is, you perform the steps a user is supposed to do (such as a click).

    您可以执行 ,即执行用户应该执行的步骤(例如单击)。

  3. You make assertions on what is supposed to happen.

    您对应该发生的事情做出断言

安排 (Arrange)

In our test, we’ve done two tasks in the arrange part:

在我们的测试中,我们在安排部分完成了两项任务:

  1. Render the component.

    渲染组件。
  2. Get the different elements of the DOM needed using queries and screen.

    使用查询和screen获得所需的DOM的不同元素。

We can render our component with the render method, which is part of RTL's API, where ui is the component to mount:

我们可以使用render方法渲染组件,该方法是RTL API的一部分,其中ui是要挂载的组件:

function render(
ui: React.ReactElement,
options?: Omit<RenderOptions, 'queries'>
): RenderResult

We can provide some options to render, but they are not often needed, so I'll let you check out what's possible in the docs.

我们可以提供一些render选项,但是并不经常需要它们,因此,我将让您了解docs中可能的功能 。

Basically, all this function does is render your component using ReactDOM.render (or hydrate, for server-side rendering) in a newly created div appended directly to document.body. You won't often need (at least in the beginning) the result from the render method, so I'll let you check the docs for this as well.

基本上,所有此功能所做的就是在直接附加到document.body的新创建的div使用ReactDOM.render (或为服务器端渲染为ReactDOM.render来渲染组件 。 您通常不会(至少在开始时)需要render方法的结果,所以我也将让您检查此文档 。

Once our component is rendered correctly, we can get the different elements of the DOM using screen queries.

正确呈现组件后,我们可以使用屏幕查询来获取DOM的不同元素。

But what is screen? As said above, the component is rendered in document.body. Since it's common to query it, Testing Library exports an object with every query pre-bound to document.body. Note that we can also destructure queries from the render result, but trust me, it's more convenient to use screen.

但是什么是screen ? 如上所述,该组件在document.body呈现。 由于查询是很常见的,因此测试库会导出一个对象,每个查询都预先绑定到document.body 。 注意,我们也可以从 render 结果中解构查询 ,但是请相信我,使用screen更加方便。

And now, you may think, what are these queries? They are utilities that allow you to query the DOM as a user would do it. Thus, you can find elements by label text, by a placeholder, by title.

现在,您可能会想, 这些查询是什么? 它们是实用程序,使您可以像查询用户一样查询DOM。 因此,您可以通过标签文本,占位符,标题来查找元素。

Here are some queries examples taken from the docs:

以下是一些来自docs的查询示例:

  • getByLabelText: searches for the label that matches the given text passed as an argument and then finds the element associated with that label

    getByLabelText :搜索与作为参数传递的给定文本匹配的标签,然后找到与该标签关联的元素

  • getByText: searches for all elements with a text node with textContent matching the given text passed as an argument

    getByText :搜索文本节点与textContent匹配作为参数传递的给定文本的所有元素

  • getByTitle: returns the element with a title attribute matching the given text passed as an argument

    getByTitle :返回具有title属性的元素,该属性与作为参数传递的给定文本匹配

  • getByPlaceholderText: searches for all elements with a placeholder attribute and finds one that matches the given text passed as an argument

    getByPlaceholderText :搜索具有placeholder属性的所有元素,并找到与作为参数传递的给定文本匹配的元素

There are many variants to a particular query:

特定查询有许多变体:

  • getBy: returns the first matching node for a query, throws an error if no elements match or finds more than one match

    getBy :返回查询的第一个匹配节点,如果没有元素匹配或发现多个匹配项,则引发错误

  • getAllBy: returns an array of all matching nodes for a query and throws an error if no elements match

    getAllBy :返回查询的所有匹配节点的数组,如果没有元素匹配则抛出错误

  • queryBy: returns the first matching node for a query and returns null if no elements match. This is useful for asserting an element that is not present.

    queryBy :返回查询的第一个匹配节点,如果没有元素匹配,则返回null。 这对于断言不存在的元素很有用。

  • queryAllBy: returns an array of all matching nodes for a query and returns an empty array ([]) if no elements match

    queryAllBy :返回查询的所有匹配节点的数组,如果没有元素匹配,则返回一个空数组( [] )

  • findBy: returns a promise, which resolves when an element is found which matches the given query

    findBy :返回一个promise,该promise在找到与给定查询匹配的元素时解析

  • findAllBy: returns a promise, which resolves to an array of elements when any elements are found which match the given query

    findAllBy :返回一个promise,当找到与给定查询匹配的任何元素时,它将解析为元素数组

Using the right query at the right time can be challenging. I highly recommend that you check Testing Playground to better know which queries to use in your apps.

在正确的时间使用正确的查询可能具有挑战性。 我强烈建议您选中“ 测试游乐场”以更好地了解在应用程序中使用哪些查询。

Let’s come back to our example:

让我们回到我们的例子:

render(<Counter />)
const counter = screen.getByText("0")
const incrementButton = screen.getByText("+")
const decrementButton = screen.getByText("-")

In this example, we can see that we first render the <Counter/>. The base element of this component will look like the following:

在此示例中,我们可以看到我们首先呈现了<Counter/> 。 该组件的基本元素如下所示:

<body>
<div>
<Counter />
</div>
</body>

Then, thanks to screen.getByText, we can query from document.body the increment button, the decrement button, and the counter. Hence, we will get for each button an instance of HTMLButtonElement and, for the counter, an instance of HTMLParagraphElement.

然后, screen.getByText ,我们可以从document.body查询递增按钮,递减按钮和计数器。 因此,我们将为每个按钮获得一个HTMLButtonElement实例,对于计数器,将获得HTMLParagraphElement实例。

法案 (Act)

Now that everything is set up, we can act. For that, we use fireEvent from DOM Testing Library:

现在一切都准备就绪,我们可以采取行动。 为此,我们使用DOM Testing Library中的 fireEvent

fireEvent((node: HTMLElement), (event: Event))

Simply put, this function takes a DOM node (that you can query with the queries seen above) and fires DOM events such as click, focus, change, etc. There are many other events you can dispatch that you can find by reading DOM Testing Library source code.

简而言之,该函数需要一个DOM节点(您可以使用上面看到的查询进行查询)并触发DOM事件,例如clickfocuschange等。您可以通过阅读DOM Testing来查找许多其他事件。 库源代码 。

Our example is relatively simple. We just want to click a button, so we simply do:

我们的例子相对简单。 我们只想单击一个按钮,因此只需执行以下操作:

fireEvent.click(incrementButton)
// OR
fireEvent.click(decrementButton)

断言 (Assert)

Here comes the last part. Firing an event usually triggers some changes in your app. We must do some assertions to make sure these changes happened. In our test, a good way to do so is to make sure the count rendered to the user has changed. Thus we just have to assert the textContent property of counter is incremented or decrement:

这是最后一部分。 触发事件通常会触发您的应用程序中的某些更改。 我们必须做一些断言,以确保发生这些变化。 在我们的测试中,这样做的一个好方法是确保呈现给用户的计数已更改。 因此,我们只需要声明countertextContent属性是递增还是递减:

expect(counter.textContent).toEqual("1")
expect(counter.textContent).toEqual("0")

And ta-da! We successfully wrote a test that doesn’t test implementation details.

和塔达! 我们成功编写了一个不测试实现细节的测试。

测试待办事项应用 (Test a To-Do App)

Let’s go deeper into this part by testing a more complex example. The app we’re going to test is a simple to-do app whose features are the following:

让我们通过测试一个更复杂的示例来更深入地研究这一部分。 我们将要测试的应用是一个简单的待办应用,其功能如下:

  • Add a new to-do.

    添加一个新的待办事项。
  • Mark a to-do as completed or active.

    将待办事项标记为已完成或有效。
  • Remove a to-do.

    删除待办事项。
  • Filter the to-dos: all, active, and done to-dos.

    过滤待办事项:所有,活动和已完成的待办事项。

Yes, I know, you may be sick of to-do apps in every tutorial, but hey, they’re great examples!

是的,我知道,您可能会讨厌每个教程中的待办事项应用程序,但嘿,它们是很好的例子!

Here is the code:

这是代码:

import React from "react";function Todos({ todos: originalTodos }) {const filters = ["all", "active", "done"];const [input, setInput] = React.useState("");const [todos, setTodos] = React.useState(originalTodos || []);const [activeFilter, setActiveFilter] = React.useState(filters[0]);const addTodo = (e) => {if (e.key === "Enter" && input.length > 0) {setTodos((todos) => [{ name: input, done: false }, ...todos]);setInput("");}};const filteredTodos = React.useMemo(() =>todos.filter((todo) => {if (activeFilter === "all") {return todo;}if (activeFilter === "active") {return !todo.done;}return todo.done;}),[todos, activeFilter]);const toggle = (index) => {setTodos((todos) =>todos.map((todo, i) =>index === i ? { ...todo, done: !todo.done } : todo));};const remove = (index) => {setTodos((todos) => todos.filter((todo, i) => i !== index));};return (<div><h2 className="title">To-dos</h2><inputclassName="input"onChange={(e) => setInput(e.target.value)}onKeyDown={addTodo}value={input}placeholder="Add something..."/><ul className="list-todo">{filteredTodos.length > 0 ? (filteredTodos.map(({ name, done }, i) => (<li key={`${name}-${i}`} className="todo-item"><inputtype="checkbox"checked={done}onChange={() => toggle(i)}id={`todo-${i}`}/><div className="todo-infos"><labelhtmlFor={`todo-${i}`}className={`todo-name ${done ? "todo-name-done" : ""}`}>{name}</label><button className="todo-delete" onClick={() => remove(i)}>Remove</button></div></li>))) : (<p className="no-results">No to-dos!</p>)}</ul><ul className="list-filters">{filters.map((filter) => (<likey={filter}className={`filter ${activeFilter === filter ? "filter-active" : ""}`}onClick={() => setActiveFilter(filter)}>{filter}</li>))}</ul></div>);
}export default Todos

关于fireEvent的更多信息 (More on fireEvent)

We saw previously how fireEvent allows us to click on a button queried with RTL queries (such as getByText). Let's see how to use other events.

前面我们看到了fireEvent如何允许我们单击RTL查询(如getByText )查询的按钮。 让我们看看如何使用其他事件。

In this app, we can add a new to-do by writing something in the input and pressing the Enter key. We'll need to dispatch two events:

在此应用中,我们可以通过在输入中编写内容并按Enter键来添加新的待办事项。 我们需要调度两个事件:

  • change to add a text in the input

    change以在输入中添加文本

  • keyDown to press the enter key

    keyDown按Enter键

Let’s write the first part of the test:

让我们编写测试的第一部分:

test("adds a new to-do", () => {render(<Todos />)const input = screen.getByPlaceholderText(/add something/i)const todo = "Read Master React Testing"screen.getByText("No to-dos!")fireEvent.change(input, { target: { value: todo } })fireEvent.keyDown(input, { key: "Enter" })
})

In this code, we:

在此代码中,我们:

  1. Query the input by its placeholder.

    通过其占位符查询输入。
  2. Declare the to-do we’re going to add.

    声明我们要添加的待办事项。
  3. Assert there were no to-dos using getByText. (If No to-dos! was not in the app, getByText would throw an error.)

    使用getByText没有待办事项。 (如果应用程序中没有“ No to-dos! ”,则getByText会引发错误。)

  4. Add the to-do in the input.

    在输入中添加待办事项。
  5. Press the enter key.

    按输入键。

One thing that may surprise you is the second argument we pass to fireEvent. Maybe you would expect it to be a single string instead of an object with a target property.

让您惊讶的一件事是我们传递给fireEvent的第二个参数。 也许您希望它是单个字符串,而不是具有target属性的对象。

Well, under the hood, fireEvent dispatches an event to mimic what happens in a real app (it makes use of the dispatchEvent method). Thus, we need to dispatch the event as it would happen in our app, including setting the target property. The same logic goes for the keyDown event and the key property.

好吧,在fireEventfireEvent调度了一个事件来模仿真实应用程序中发生的事情(它利用了dispatchEvent方法)。 因此,我们需要像在应用程序中那样调度事件,包括设置target属性。 keyDown事件和key属性的逻辑相同。

What should happen if we add a new to-do?

如果添加新的待办事项应该怎么办?

  • There should be a new item in the list.

    列表中应该有一个新项目。
  • The input should be empty.

    输入应为空。

Hence, we need to query somehow the new item in the DOM and make sure the value property of the input is empty:

因此,我们需要以某种方式查询DOM中的新项目,并确保输入的value属性为空:

screen.getByText(todo)
expect(input.value).toBe("")

The full test becomes:

完整的测试变为:

test("adds a new to-do", () => {render(<Todos />)const input = screen.getByPlaceholderText(/add something/i)const todo = "Read Master React Testing"screen.getByText("No to-dos!")fireEvent.change(input, { target: { value: todo } })fireEvent.keyDown(input, { key: "Enter" })screen.getByText(todo)expect(input.value).toBe("")
})

开玩笑更好的断言 (Better assertions with jest-dom)

The more you’ll write tests with RTL, the more you’ll have to write assertions for your different DOM nodes. Writing such assertions can sometimes be repetitive and a bit hard to read. For that, you can install another testing library tool called jest-dom.

使用RTL编写测试的次数越多,则必须为不同的DOM节点编写断言的次数就越多。 编写这样的断言有时可能是重复的,并且有点难以阅读。 为此,您可以安装另一个名为jest-dom的测试库工具。

jest-dom provides a set of custom Jest matchers that you can use to extend Jest. These will make your tests more declarative, clearer to read, and easier to maintain.

jest-dom提供了一组可用于扩展Jest的自定义Jest匹配器。 这些将使您的测试更具声明性,更易于阅读,并且更易于维护。

There are many matchers you can use, such as:

您可以使用许多匹配器,例如:

  • toBeInTheDocument

    toBeInTheDocument

  • toBeDisabled

    toBeDisabled

  • toHaveTextContent

    toHaveTextContent

  • toHaveValue

    toHaveValue

  • And more

    和更多

You can install it with the following command:

您可以使用以下命令进行安装:

npm install --save-dev @testing-library/jest-dom

Then you have to import the package once to extend the Jest matchers:

然后,您必须一次导入软件包以扩展Jest匹配器:

import "@testing-library/jest-dom/extend-expect"

Note: I recommend that you do that in src/setupTests.js if you use Create React App. If you don't use CRA, import it in one of the files defined in the setupFilesAfterEnv key of your Jest config.

注意 :如果使用Create React App ,我建议您在src/setupTests.js执行此操作。 如果不使用CRA,则将其导入Jest配置的setupFilesAfterEnv键中定义的文件之一。

Let’s come back to our test. By installing jest-dom, your assertion would become:

让我们回到测试中。 通过安装jest-dom ,您的断言将变为:

expect(input).toHaveValue("")

It’s not much, but it’s more readable, it’s convenient, and it improves the developer experience.

它虽然不多,但更具可读性,便利性并改善了开发人员的体验。

Note: If you want to see more test examples on this to-do app, I created a repo that contains all the examples of this article.

注意: 如果您想在此待办事项应用程序上查看更多测试示例,我创建了一个包含本文所有示例的存储库 。

异步测试 (Asynchronous Tests)

I agree the counter and the to-do app are contrived examples. In fact, most real-world applications involve asynchronous actions: data fetching, lazy-loaded components, etc. Thus you need to handle them in your tests.

我同意柜台和待办事项应用程式是人为的例子。 实际上,大多数实际应用程序都涉及异步操作:数据获取,延迟加载的组件等。因此,您需要在测试中处理它们。

Luckily for us, RTL gives us asynchronous utilities such as waitForElementToBeRemoved.

对我们来说幸运的是,RTL为我们提供了异步工具,例如waitForElementToBeRemoved

In this part, we will use a straightforward posts app whose features are the following:

在这一部分中,我们将使用一个简单的帖子应用程序,其功能如下:

  • Create a post.

    创建一个帖子。
  • See the newly created post in a list of posts.

    在帖子列表中查看新创建的帖子。
  • See an error if something has gone wrong while creating the post.

    创建帖子时,如果出现问题,请查看错误。

Here is the code:

这是代码:

let nextId = 0export const addPost = (post) => {return new Promise((resolve, reject) => {setTimeout(() => {if (Math.random() > 0.1) {resolve({ status: 200, data: { ...post, id: nextId++ } })} else {reject({status: 500,data: "Something wrong happened. Please, retry.",})}}, 500)})
}

Let’s test the post creation feature. To do so, we need to:

让我们测试帖子创建功能。 为此,我们需要:

  1. Mock the API to make sure a post creation doesn’t fail.

    模拟API以确保发布后创建不会失败。
  2. Fill in the tile.

    填写瓷砖。
  3. Fill in the content of the post.

    填写帖子内容。
  4. Click the Post button.

    单击发布按钮。

Let’s first query the corresponding elements:

让我们首先查询相应的元素:

import React from "react"
import { fireEvent, render, screen } from "@testing-library/react"
import { addPost as addPostMock } from "./api"
import Posts from "./Posts"
jest.mock("./api")
describe("Posts", () => {test("adds a post", async () => {addPostMock.mockImplementation((post) =>Promise.resolve({ status: 200, data: { ...post, id: 1 } }))render(<Posts />)const title = screen.getByPlaceholderText(/title/i)const content = screen.getByPlaceholderText(/post/i)const button = screen.getByText(/post/i)const postTitle = "This is a post"const postContent = "This is the content of my post"})
})

You can see I’ve used queries differently this time. Indeed, when you pass a string to a getBy query, it expects to match exactly that string. If there's something wrong with one character, then the query fails.

您可以看到这次我使用查询的方式有所不同。 确实,当您将字符串传递给getBy查询时,它期望与该字符串完全匹配。 如果一个字符有问题,则查询失败。

However, the queries also accept a regular expression as an argument. It can be handy if you want to quickly query a long text or if you want to query a substring of your sentence in case you’re still not sure of the wording.

但是,查询还接受正则表达式作为参数。 如果您想快速查询长文本或想要查询句子的子字符串,这可能会很方便,以防您仍然不确定措辞。

For example, I know the placeholder of my content should include the word post. But, maybe the placeholder will see its wording change at some point, and I don’t want my tests to break because of this simple change. So I use:

例如,我知道内容的占位符应包含单词post 。 但是,也许占位符会在某个时候看到其措词更改,并且我不希望因为这种简单的更改而导致测试失败。 所以我用:

const content = screen.getByPlaceholderText(/post/i)

Note: For the same reason, I use i to make the search case-insensitive. That way, my test doesn't fail if the case changes. Caution, though! If the wording is important and shouldn't change, don't make use of regular expressions.

注意:出于相同的原因,我使用i来使搜索不区分大小写。 这样,如果情况发生变化,我的测试就不会失败。 但是要小心! 如果措辞很重要且不应更改,请不要使用正则表达式。

Then we have to fire the corresponding events and make sure the post has been added. Let’s try it out:

然后,我们必须触发相应的事件,并确保已添加该帖子。 让我们尝试一下:

test("adds a post", () => {// ...const postContent = "This is the content of my post"fireEvent.change(title, { target: { value: postTitle } })fireEvent.change(content, { target: { value: postContent } })fireEvent.click(button)// Oops, this will fail ❌expect(screen.queryByText(postTitle)).toBeInTheDocument()expect(screen.queryByText(postContent)).toBeInTheDocument()
})

If we had run this test, it wouldn’t have worked. In fact, RTL can’t query our post title. But why? To answer that question, I’ll have to introduce you to one of your next best friends: debug.

如果我们运行了该测试,它将无法正常工作。 实际上,RTL无法查询我们的帖子标题。 但为什么? 要回答这个问题,我将向您介绍您的下一个最好的朋友: debug

调试测试 (Debugging tests)

Simply put, debug is a utility function attached to the screen object that prints out a representation of your component's associated DOM. Let's use it:

简而言之, debug是一个附加到screen对象的实用程序功能,可以打印出与组件关联的DOM的表示形式。 让我们使用它:

test("adds a post", () => {// ...fireEvent.change(title, { target: { value: postTitle } })fireEvent.change(content, { target: { value: postContent } })fireEvent.click(button)debug()expect(screen.queryByText(postTitle)).toBeInTheDocument()expect(screen.queryByText(postContent)).toBeInTheDocument()
})

In our case, debug outputs something similar to this:

在我们的情况下, debug输出类似于以下内容:

<body><div><div><form class="form"><h2>Say something</h2><input placeholder="Your title" type="text" /><textarea placeholder="Your post" rows="5" type="text" /<button class="btn" disabled="" type="submit">Post ing...</button></form><div /></div></div>
</body>

Now that we know what your DOM looks like, we can guess what’s happening. The post hasn’t been added. If we closely pay attention, we can see the button’s text is now Posting instead of Post.

既然我们知道您的DOM的外观,我们就可以猜测发生了什么。 该帖子尚未添加。 如果我们密切注意,我们可以看到按钮的文本现在是Posting而不是Post

Do you know why? Because posting a post is asynchronous, and we’re trying to execute the tests without waiting for the asynchronous actions. We’re just in the Loading phase. We can only make sure some stuff is going on:

你知道为什么吗? 因为发布帖子是异步的,所以我们试图在不等待异步操作的情况下执行测试。 我们正处于加载阶段。 我们只能确保发生了一些事情:

test("adds a post", () => {
// ...
fireEvent.click(button) expect(button).toHaveTextContent("Posting")
expect(button).toBeDisabled()
})

等待变更 (Wait for changes)

We can do something about that. More precisely, RTL can do something about that with asynchronous utilities such as waitFor:

我们可以为此做些事情。 更准确地说,RTL可以使用异步工具 (如waitFor来做一些事情:

function waitFor<T>(callback: () => void,options?: {container?: HTMLElementtimeout?: numberinterval?: numberonTimeout?: (error: Error) => ErrormutationObserverOptions?: MutationObserverInit}
): Promise<T>

Simply put, waitFor takes a callback which contains expectations and waits for a specific time until these expectations pass.

简而言之, waitFor接受一个包含期望的回调,并等待特定的时间,直到这些期望通过。

By default, this time is at most 1000ms at an interval of 50ms (the first function call is fired immediately). This callback is also run every time a child is added or removed in your component's container using MutationObserver.

默认情况下,此时间最多为1000ms,间隔为50ms(第一个函数调用立即触发)。 每当使用MutationObserver在组件的container添加或删除子项时,也会运行此回调。

We’re going to make use of that function and put our initial assertions in it. The test now becomes:

我们将利用该函数并将初始断言放入其中。 现在测试变为:

import React from "react"
import { fireEvent, render, screen, waitFor } from "@testing-library/react"
// ...
describe("Posts", () => {test("adds a post", async () => {// ...expect(button).toHaveTextContent("Posting")expect(button).toBeDisabled()await waitFor(() => {screen.getByText(postTitle)screen.getByText(postContent)})})
})

If you’re using CRA, maybe you encountered the following error:

如果您使用的是CRA,则可能会遇到以下错误:

TypeError: MutationObserver is not a constructor

That’s normal. DOM Testing Library v7 removed a shim of MutationObserver as it's now widely supported. However, CRA, as the time of writing, still uses an older version of Jest (24 or before) which itself uses a JSDOM environment where MutationObserver doesn't exist.

那很正常 DOM测试库v7删除了MutationObserver的填充程序,因为它现在得到了广泛的支持。 但是,在撰写本文时,CRA仍使用旧版本的Jest(24或更早版本),而Jest本身使用的是不存在MutationObserver的JSDOM环境。

Two steps to fix it. First, install jest-environment-jsdom-sixteen as a dev dependency. Then update your test script in your package.json file:

修复它的两个步骤。 首先,安装jest-environment-jsdom-sixteen作为dev依赖项。 然后在package.json文件中更新test脚本:

"scripts": {
...
"test": "react-scripts test --env=jest-environment-jsdom-sixteen"
...
}

Now it passes.

现在它过去了。

There is also another way of testing asynchronous things with findBy* queries, which is just a combination of getBy* queries and waitFor:

还有另一种使用findBy* 查询测试异步事物的方法,这只是getBy*查询和waitFor的组合:

import React from "react"
import { fireEvent, render, screen, waitFor } from "@testing-library/react"
// ...
describe("Posts", () => {test("adds a post", async () => {// ...expect(button).toHaveTextContent("Posting")expect(button).toBeDisabled()await screen.findByText(postTitle)screen.getByText(postContent)})
})

Note: In the past, you could also use waitForElement but it’s deprecated now. Don't worry if you find it in certain tests.

注意:过去,您也可以使用waitForElement但现在已弃用。 如果您在某些测试中找到它,请不要担心。

We know for sure that the API successfully returned the full post after the await statement, so we don't have to put async stuff after.

我们肯定知道API在await语句之后成功返回了完整的帖子,因此我们不必在此之后放置异步内容。

And remember, findByText is asynchronous. If you find yourself forgetting the await statement a little bit too much, I encourage you to install the plugin eslint-plugin-testing-library, which contains a rule that prevents you from doing so.

记住, findByText是异步的。 如果您发现自己忘记了await语句太多,我建议您安装eslint-plugin-testing-library插件 ,该插件包含阻止您这样做的规则 。

Phew! That part was not easy.

! 那部分并不容易。

Hopefully, these three examples allowed you to have an in-depth look at how you can start to write tests for your React apps, but that’s just the tip of the iceberg. A complex app often makes use of React Router, Redux, React's Context, third-party libraries (react-select, for example). Kent C. Dodds has a complete course on that (and much more) called Testing JavaScript that I really recommend.

希望通过这三个示例,您可以深入了解如何开始为React应用编写测试,但这只是冰山一角。 一个复杂的应用程序经常利用React Router , Redux ,React的Context和第三方库(例如react-select )。 肯特C.多德斯(Kent C. Dodds)在这方面有一本完整的课程(以及更多),我真的建议您称之为测试JavaScript 。

翻译自: https://medium.com/better-programming/the-complete-beginners-guide-to-testing-react-apps-10964f515bec

初学react实现路由跳转

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Sqoop-1.4.6安装部署及详细使用介绍

    之所以选择Sqoop1是因为Sqoop2目前问题太多。无法正常使用,综合比较后选择Sqoop1。 Sqoop1安装配置比较简单 一、安装部署 (1)、下载地址:http://archive.cloudera.com/cdh5/cdh/5/sqoop-1.4.6-cdh5.5.2.tar.gz 解压到/opt/cdh5/sqoop (2)、拷贝mysql的jdbc驱动包mysql-…...

    2024/5/1 6:00:19
  2. cnpm安装时候出现“Unexpected end of JSON input“的解决办法

    在控制台输入一下命令&#xff1a; $ npm cache clean --force 输入运行之后再次安装淘宝镜像即可成功 $ npm install -g cnpm --registryhttps://registry.npm.taobao.org 希望能够帮到你们...

    2024/5/1 12:45:17
  3. 持续更新,本人感觉需要的网站

    1.程序员相关书籍学习网站地址&#xff0c;免费的编程中文书 2.jquery源码查看网址 3.nodejs的express配合websocket使用 4.判断鼠标移入移出时的位置案例 5、控制元素视差滚动效果 6、angular中文文档 7、webpack的配置 8、ionic官方网站 9、ionic中文翻译网站 10、i…...

    2024/5/1 9:29:45
  4. fastjson 反序列化导致任意命令执行漏洞

    fastjson 反序列化导致任意命令执行漏洞原理http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/https://github.com/shengqi158/fastjson-remote-code-e…...

    2024/5/1 11:20:56
  5. 双眼皮埋线疤痕多久会消失了

    ...

    2024/5/1 8:28:17
  6. 双眼皮埋线 眼皮薄

    ...

    2024/5/1 6:06:20
  7. 国内外优秀前端 CDN 加速服务

    2017年03月03日 鬼知道 360 CDN 什么时候又抽了&#xff08;时好时坏&#xff0c;极慢&#xff01;&#xff09; 现在 Google Fonts 在北京已有电信服务器&#xff0c;访问速度快快的&#xff0c;鼓掌&#xff01; 新增一个 CDN&#xff0c;一“套”带走&#xff01; 点这里&am…...

    2024/4/20 14:25:39
  8. 2014 年最热门的国人开发开源软件TOP 100

    不知道从什么时候开始&#xff0c;很多一说起国产好像就非常愤慨&#xff0c;其实大可不必。做开源中国六年有余&#xff0c;这六年时间国内的开源蓬勃发展&#xff0c;从一开始的使用到贡献&#xff0c;到推出自己很多的开源软件&#xff0c;而且还有很多软件被国外认可。中国…...

    2024/5/1 10:08:08
  9. HyBrid App框架介绍

    如果篮球架和人同高&#xff0c;则无法引起人们的兴趣&#xff1b;但如果篮球架有两层楼那么高&#xff0c;同样是人丧失兴趣。——篮球架效应 RuiMan的博客 - [混合开发]HybridApp分析&#xff01;&#xff01;&#xff01; kingplus - Hybrid APP混合开发的一些经验和总结ch…...

    2024/4/24 23:16:15
  10. 一位Java开发攻城狮的自我修养之项目篇

    攻城狮的自我修养之项目篇基础知识Java基础SpingBootSpringCloudLeetCode题库大数据项目实战商城系统权限管理系统脚手架系统人事管理系统论坛系统博客系统微服务微服务框架服务治理框架API网关分布式管理分布式任务调度消息中间件搜索引擎数据库数据库连接池分布式数据库Redis…...

    2024/5/1 15:59:40
  11. 双眼皮留坑了怎么办

    ...

    2024/5/1 8:38:13
  12. 双眼皮留坑还会好吗

    ...

    2024/5/1 7:49:51
  13. 双眼皮留坑

    ...

    2024/4/21 16:24:49
  14. 双眼皮两层肉

    ...

    2024/4/23 6:01:05
  15. 双眼皮脸型变了

    ...

    2024/4/29 17:18:18
  16. angular——更多按钮的上拉菜单(路由跳转)

    <button class"btn gray_text_btn list_item" ng-click"action.toMoreOptions()"><i class"icon ion-navicon"></i> </button> <!-------------------- 底部按钮 -----------------------><section class&qu…...

    2024/4/26 1:35:20
  17. HybridAPP:AngularJS+Ionic+Cordova开发学习【三】添加Tab和图标

    底部Tab只有3个&#xff0c;现在准备新添加一页&#xff0c;并设置图标 1. 首先创建Setting页面组建。 我没有新建&#xff0c;而好似复制的src/pages下的home文件夹&#xff0c;把代码中home相关的改成setting&#xff0c;变OK了 2. 设置新页面组建的全局声明 3. 在tabs.html…...

    2024/4/23 12:08:09
  18. ionic3 登录返回实现案例(解决tabs底栏在返回后显示的问题)

    在制作登录、注销功能时&#xff0c;很容易出现tabs底栏在注销后仍然显示的问题&#xff0c;查阅很多资料&#xff0c;但是很多信息都是支离破碎的&#xff0c;虽然有人解决了这个问题&#xff0c;但是由于描述不清&#xff0c;大量有用的信息被隐藏在解决办法的背后。 经过整…...

    2024/4/21 16:24:43
  19. 双眼皮脸变大吗

    ...

    2024/4/29 4:50:15
  20. 双眼皮脸变大

    ...

    2024/4/25 14:42:08

最新文章

  1. VS code 同步odata服务

    在做UI5得开发过程中&#xff0c;经常会出现odata需要更新 那么已经加载过得项目如何去跟新odata服务呢 可以通过如下步骤 1.右键打开应用信息 2.找到manage service models 3.点击编辑 4.选中 刷新并保存...

    2024/5/1 17:57:58
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. SpringMVC初始化工程

    SpringMVC初始化工程 本文采用maven作为构建工具,SpringMVC作为主框架。 创建一个maven的web工程,并配置pom文件<!-- pom.xml --> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0&qu…...

    2024/4/29 13:35:34
  4. Django实现的登录注册功能

    1 前言 在Web开发中&#xff0c;用户登录和注册是最基本且必不可少的功能。Django&#xff0c;作为一个高级的Python Web框架&#xff0c;为我们提供了强大的工具和库来快速实现这些功能。下面&#xff0c;我将详细介绍如何使用Django来实现用户登录和注册功能。 2 功能介绍 …...

    2024/5/1 13:23:09
  5. [C++/Linux] UDP编程

    一. UDP函数 UDP&#xff08;用户数据报协议&#xff0c;User Datagram Protocol&#xff09;是一种无连接的网络协议&#xff0c;用于在互联网上交换数据。它允许应用程序发送数据报给另一端的应用程序&#xff0c;但不保证数据报能成功到达&#xff0c;也就是说&#xff0c;它…...

    2024/4/30 4:30:51
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/30 18:21:48
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/30 9:43:22
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57