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

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.
主站蜘蛛池模板: 塔城市| 龙井市| 灵石县| 历史| 贡山| 洛川县| 香格里拉县| 靖州| 莱州市| 甘泉县| 达拉特旗| 镇坪县| 积石山| 泗水县| 南江县| 甘德县| 葫芦岛市| 故城县| 新沂市| 河东区| 蓬莱市| 康平县| 敖汉旗| 泽库县| 刚察县| 信丰县| 库伦旗| 铁岭县| 南和县| 泸定县| 保靖县| 太谷县| 青神县| 饶阳县| 宁城县| 滦平县| 云龙县| 丰原市| 南阳市| 榆树市| 澄江县|