- Mastering React Test:Driven Development
- Daniel Irvine
- 896字
- 2021-06-24 14:45:05
Adding events to a functional component
We're about to add state to our component. The component will show a button for each appointment. When the button is clicked, the component stores the array index of the appointment that it refers to. To do that, we'll use the useState hook.
If you're new to hooks, check out the Further learning section at the end of this chapter. Alternatively, you could just follow along and see how much you can pick up just by reading the tests!
Let's start by asserting that each li element has a button element:
- Add the following test, just below the last one you added. The second expectation is a little peculiar in that it is checking the type of the button to be button. If you haven't seen this before, it's idiomatic when using button elements to define its role by setting the type attribute, as I'm doing here:
it('has a button element in each li', () => {
render(<AppointmentsDayView appointments={appointments} />);
expect(
container.querySelectorAll('li > button')
).toHaveLength(2);
expect(
container.querySelectorAll('li > button')[0].type
).toEqual('button');
});
- Make this test pass by modifying the AppointmentsDayView return value, as shown:
<ol>
{appointments.map(appointment => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment.startsAt)}
</button>
</li>))}
</ol>;
- We can now test what happens when the button is clicked. Back in test/Appointment.test.js, add the following as the next test. This uses the ReactTestUtils.Simulate.click function to perform the click action:
it('renders another appointment when selected', () => {
render(<AppointmentsDayView appointments={appointments} />);
const button = container.querySelectorAll('button')[1];
ReactTestUtils.Simulate.click(button);
expect(container.textContent).toMatch('Jordan');
});
- Include the following import at the top of test/Appointment.test.js:
import ReactTestUtils from 'react-dom/test-utils';
- Go ahead and run the test:
● AppointmentsDayView ? renders appointment when selected
expect(received).toMatch(expected)
Expected value to match:
"Jordan"
Received:
"12:0013:00Ashley"
We're getting all of the list content dumped out too, because we've used container.textContent in our expectation rather than something more specific.
There's a lot we now need to get in place in order to make the test pass: we need to introduce state and we need to add the handler. But, first, we'll need to modify our definition to use a block with a return statement:
- Set the last test to skip, using it.skip.
It may seem a little pedantic to do that for the very tiny change we're about to make, but it's good practice.
- Wrap the constant definition in curly braces, and then return the existing value. Once you've made this change, run your tests and check you're all green:
export const AppointmentsDayView = ({ appointments }) => {
return (
<div id="appointmentsDayView">
<ol>
{appointments.map(appointment => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment)}
</button>
</li>))}
</ol>
<Appointment customer={appointments[0].customer} />
</div>
);
};
- Unskip the latest test by changing it.skip to it, and let's get to work on making it pass.
- Update the import at the top of the file to pull in the useState function:
import React, { useState } from 'react';
- Add the following line above the return statement:
const [selectedAppointment, setSelectedAppointment] = useState(
0
);
- We can now use this selectedAppointment rather than hard-coding an index selecting the right appointment. Change the return value to use this new state value when selecting an appointment:
<div id="appointmentsDayView">
...
<Appointment {...appointments[selectedAppointment]} />
</div>
- Then, change the map call to include an index in its arguments. Let's just name that i:
{appointments.map((appointment, i) => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment.startsAt)}
</button>
</li>
))}
- Now call setSelectedAppointment from within the onClick handler on the button element:
<button
type="button"
onClick={() => setSelectedAppointment(i)}>
- Run your tests, and you should find they're all green:
PASS test/Appointment.test.js
Appointment
? renders the customer first name (18ms)
? renders another customer first name (2ms)
AppointmentsDayView
? renders a div with the right id (7ms)
? renders multiple appointments in an ol element (16ms)
? renders each appointment in an li (4ms)
? initially shows a message saying there are no appointments today (6ms)
? selects the first element by default (2ms)
? has a button element in each li (2ms)
? renders another appointment when selected (3ms)
Our component is now complete and ready to be used in the rest of our application. That is, once we've built the rest of the application!
- C#高級(jí)編程(第10版) C# 6 & .NET Core 1.0 (.NET開(kāi)發(fā)經(jīng)典名著)
- JavaScript高效圖形編程
- 移動(dòng)UI設(shè)計(jì)(微課版)
- Ceph Cookbook
- 零基礎(chǔ)學(xué)Java(第4版)
- JS全書(shū):JavaScript Web前端開(kāi)發(fā)指南
- KnockoutJS Starter
- 單片機(jī)應(yīng)用與調(diào)試項(xiàng)目教程(C語(yǔ)言版)
- 快人一步:系統(tǒng)性能提高之道
- Raspberry Pi Robotic Blueprints
- JavaScript動(dòng)態(tài)網(wǎng)頁(yè)編程
- 遠(yuǎn)方:兩位持續(xù)創(chuàng)業(yè)者的點(diǎn)滴思考
- Deep Learning for Natural Language Processing
- 數(shù)據(jù)結(jié)構(gòu):Python語(yǔ)言描述
- Puppet 5 Beginner's Guide(Third Edition)