官术网_书友最值得收藏!

Rendering the list of appointments

We'll add our new component into the same file we've been using already because there's not much code in there so far.

We don't always need a new file for each component, particularly when the components are short functional components, such as our Appointment component (a one-line function). It can help to group related components or small sub-trees of components in one place.

In test/Appointment.test.js, create a new describe block under the first one, with a single test, as follows. This test checks that we render a div with a particular ID. That's important in this case because we load a CSS file that looks for this element. The expectations in this test use the DOM method, querySelector. This searches the DOM tree for a single element with the tag provided:

describe('AppointmentsDayView', () => {
let container;

beforeEach(() => {
container = document.createElement('div');
});

const render = component =>
ReactDOM.render(component, container);

it('renders a div with the right id', () => {
render(<AppointmentsDayView appointments={[]} />);
expect(container.querySelector('div#appointmentsDayView')).not.toBeNull();
});
});
It isn't always necessary to wrap your component in a div with an ID or a class. I tend to do it when I have CSS that I want to attach to the entire group of HTML elements that will be rendered by the component, which, as you'll see later, is the case for AppointmentsDayView .

This test uses the exact same render function from the first describe block, as well as the same let container declaration and beforeEach block. In other words, we've introduced duplicated code. By duplicating code from our first test suite, we're making a mess straight after cleaning up our code! Well, we're allowed to do it when we're in the first stage of the TDD cycle. Once we've got the test passing, we can think about the right structure for the code.

Run npm test and let's look at the output:

FAIL 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)

● AppointmentsDayView ? renders a div with the right id

ReferenceError: AppointmentsDayView is not defined

Let's work on getting this test to pass!

  1. To fix this, change the last import in your test file to read as follows:
import {
Appointment,
AppointmentsDayView
} from '../src/Appointment';
  1. In src/Appointment.js, add this functional component below Appointment:
export const AppointmentsDayView = () => null;
When we first defined our Appointment component earlier, we didn't return null. In fact, we didn't return anything. React then gave us a test error that we needed to fix before we got to a helpful test failure. So, returning null allows us to skip past the error from React and will bring us directly to a test failure. I'll generally begin all my components in this way—with a null value.
  1. Run your tests again:
  ● AppointmentsDayView ? renders a div with the right id

expect(received).not.toBeNull()

Received: null

47 | it('renders a div with the right id', () => {
48 | render(<AppointmentsDayView appointments={[]} />);
> 49 | expect(container.querySelector('div#appointmentsDayView')).not.toBeNull();
| ^
50 | });
  1. Finally, a test failure! Let's get that div in place:
export const AppointmentsDayView = () =>
<div id="appointmentsDayView"></div>;
  1. Your test should now be passing. Let's move on to the next test. Add the following text, just below the last test in test/Appointment.test.js, still inside the AppointmentsDayView describe block:
it('renders multiple appointments in an ol element', () => {
const today = new Date();
const appointments = [
{ startsAt: today.setHours(12, 0) },
{ startsAt: today.setHours(13, 0) }
];
render(<AppointmentsDayView appointments={appointments} />);
expect(container.querySelector('ol')).not.toBeNull();
expect(
container.querySelector('ol').children
).toHaveLength(2);
});
  1. Run your tests:
expect(received).not.toBeNull()

Received: null

57 | ];
58 | render(<AppointmentsDayView appointments={appointments} />);
> 59 | expect(container.querySelector('ol')).not.toBeNull();
| ^
60 | expect(container.querySelector('ol').children).toHaveLength(2);
61 | });
62 | });

at Object.toBeNull (test/Appointment.test.js:48:47)
In the test, the  today constant is defined to be new Date(). Each of the two records then uses this as a kind of "base" date to work its own time off. Whenever we're dealing with dates, it's important that we base all events on the same moment in time, rather than asking the system for the current time more than once. Doing that is a subtle bug waiting to happen.
  1. Let's add the ol element. Remember not to jump ahead; at this point, we just need ol to be there, not including the two items:
export const AppointmentsDayView = () => (
<div id="appointmentsDayView">
<ol />
</div>
);
  1. Run npm test again. The test output is now as follows:
Expected length: 2
Received length: 0
Received object: []

47 | render(<Appointments appointments={appointments} />);
48 | expect(container.querySelector('ol')).not.toBeNull();
> 49 | expect(container.querySelector('ol').children).toHaveLength(2);
| ^
50 | });
51 | });
52 |
  1. Since we've got multiple expectations in this test, the stack trace is essential in highlighting which expectation failed. This time, it's the second expectation: we've got zero children in the ol element but we want two. To fix this, as always, we'll do the simplest thing that will possibly work, as follows:
export const AppointmentsDayView = ({ appointments }) => (
<div id="appointmentsDayView">
<ol>
{appointments.map(() => (
<div />
))}
</ol>
</div>
);
The map function will provide a single argument to the function passed to it. Since we don't use the argument (yet), we don't need to assign it in the function signature—we can just pretend that our function has no arguments instead, hence the empty brackets. Don't worry, we'll need the argument for a subsequent test and we'll add it in then.
  1. If we're being strict, this isn't quite right: ol elements should not have div elements for children. But, that's all we should need to pass the test. We can use the next test to make sure the children are li elements. Let's see what Jest says; run npm test again:
PASS test/Appointment.test.js
Appointment
? renders the customer first name (19ms)
? renders another customer first name (2ms)
AppointmentsDayView
? renders a div with the right id (7ms)
? renders multiple appointments in an ol element (16ms)

console.error node_modules/react/cjs/react.development.js:217
Warning: Each child in an array or iterator should have a unique "key" prop.
  1. Our test passed, but we got a warning from React. It's telling us to set a key value on each li element. We can use startsAt as a key:
<ol>
{appointments.map(appointment => (
<div key={appointment.startsAt} />
))}
</ol>
Unfortunately there's no easy way for us test key values in React. To do it, we'd need to rely on internal React properties, which would make our tests at risk of breaking if the React team were to ever change those properties.

The best we can do is set a key to get rid of this warning message. Any value will do: unfortunately we can't use TDD to specify how keys are formed.

In this case, I'd quite like a test that uses the startsAt timestamp for each li key. Let's just imagine that we have that test in place.
主站蜘蛛池模板: 涪陵区| 富宁县| 泗阳县| 剑河县| 海城市| 公主岭市| 东平县| 陆川县| 嘉禾县| 洪雅县| 册亨县| 武鸣县| 益阳市| 弋阳县| 浠水县| 锡林浩特市| 高雄市| 亚东县| 烟台市| 虹口区| 永胜县| 华容县| 和顺县| 永州市| 昭通市| 海门市| 新津县| 汉沽区| 光泽县| 黔江区| 定西市| 万州区| 洛南县| 宝坻区| 黎城县| 杨浦区| 乐平市| 林州市| 台前县| 武夷山市| 颍上县|