Phát triển Javascript - part 42 ppsx

10 279 0
Phát triển Javascript - part 42 ppsx

Đang tải... (xem toàn văn)

Thông tin tài liệu

ptg 14.6 Returning to the Controller 383 test.ok(this.res.writeHead.called); test.equals(this.res.writeHead.args[0], 201); test.done(); }, "should close connection": function (test) { this.controller.respond(201); test.ok(this.res.end.called); test.done(); } }); We can pass these tests by copying the two lines we last added to post into the new respond method, as Listing 14.74 shows. Listing 14.74 A dedicated respond method var chatRoomController = { /* */ respond: function (status) { this.response.writeHead(status); this.response.end(); } }; Now we can simplify the post method by calling this method instead. Doing so also allows us to merge the original tests for status code and connection closing, by stubbing respond and asserting that it was called. 14.6.2.3 Formatting Messages Next up for the get method is properly formatting messages. Again we’ll need to lean on the cometClient, which defines the data format. The method should respond with a JSON object whose properties name the topic and values are arrays of objects. Additionally, the JSON object should include a token property. The JSON string should be written to the response body. We can formulate this as a test by stubbing respond as we did before, this time expecting an object passed as the second argument. Thus, we will need to embellish respond later, having it write its second argument to the response body as a JSON string. Listing 14.75 shows the test. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 384 Server-Side JavaScript with Node.js Listing 14.75 Expecting an object passed to respond function controllerSetUp() { var req = this.req = new EventEmitter(); req.headers = { "x-access-token": "" }; /* */ var add = this.addMessagePromise = new Promise(); var wait = this.waitForMessagesPromise = new Promise(); this.controller.chatRoom = { addMessage: stub(add), waitForMessagesSince: stub(wait) }; /* */ } /* */ testCase(exports, "chatRoomController.respond", { /* */ "should respond with formatted data": function (test) { this.controller.respond = stub(); var messages = [{ user: "cjno", message: "hi" }]; this.waitForMessagesPromise.resolve(messages); this.controller.get(); process.nextTick(function () { test.ok(this.controller.respond.called); var args = this.controller.respond.args; test.same(args[0], 201); test.same(args[1].message, messages); test.done(); }.bind(this)); } }); This test is a bit of a mouthful, and to make it slightly easier to digest, the setUp method was augmented. All the tests so far have stubbed waitForMessagesS- ince, and all of them require the headers to be set. Pulling these out makes it easier to focus on what the test in question is trying to achieve. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 14.6 Returning to the Controller 385 The test resolves the promise returned by waitForMessagesSince, and expects the resolving data to be wrapped in a cometClient friendly object and passed to the resolve method along with a 200 status. Listing 14.76 shows the required code to pass the test. Listing 14.76 Responding from get get: function () { var id = this.request.headers["x-access-token"] || 0; var wait = this.chatRoom.waitForMessagesSince(id); wait.then(function (msgs) { this.respond(200, { message: msgs }); }.bind(this)); } 14.6.2.4 Updating the Token Along with the messages, the get method needs to embed a token in its response. The token will automatically be picked up by cometClient and sent with the X-Access-Token header on subsequent requests. Listing 14.77 expects the token to be passed along with the message. Listing 14.77 Expecting a token embedded in the response "should include token in response": function (test) { this.controller.respond = stub(); this.waitForMessagesPromise.resolve([{id:24}, {id:25}]); this.controller.get(); process.nextTick(function () { test.same(this.controller.respond.args[1].token, 25); test.done(); }.bind(this)); } Passing the test involves passing the id of the last message as the token as seen in Listing 14.78. Listing 14.78 Embedding the token get: function () { /* */ wait.then(function (messages) { From the Library of WoweBook.Com Download from www.eBookTM.com ptg 386 Server-Side JavaScript with Node.js this.respond(200, { message: messages, token: messages[messages.length - 1].id }); }.bind(this)); } 14.6.3 Response Headers and Body The final missing piece of the puzzle is encoding the response data as JSON and writing the response body. I will leave TDD-ing these features into the respond method as a last exercise for this chapter. For completeness, Listing 14.79 shows one possible outcome of the respond method. Listing 14.79 The respond method respond: function (status, data) { var strData = JSON.stringify(data) || "{}"; this.response.writeHead(status, { "Content-Type": "application/json", "Content-Length": strData.length }); this.response.write(strData); this.response.end(); } And that’s it! To take the application for a spin, we can launch another command line session, as Listing 14.80 shows. Listing 14.80 Manually testing the finished app from the command line $ node-repl node> var msg = { user:"cjno", message:"Enjoying Node.js" }; node> var data = { topic: "message", data: msg }; node> var encoded = encodeURI(JSON.stringify(data)); node> require("fs").writeFileSync("chapp.txt", encoded); node> Ctrl-d $ curl -d `cat chapp.txt` http://localhost:8000/comet $ curl http://localhost:8000/comet {"message":[{"id":1,"user":"cjno",\ "message":"Enjoying Node.js"}],"token":1} From the Library of WoweBook.Com Download from www.eBookTM.com ptg 14.7 Summary 387 14.7 Summary In this chapter we have gotten toknow Node.js, asynchronous I/O for V8 JavaScript, and we have practiced JavaScript TDD outside the browser to see how the expe- rience from previous exercises fares in a completely different environment than we’re used to. By building a small web server to power a chat application we have gotten to know Node’s HTTP, Assert, and Event APIs in addition to the third party node-promise library. To provide the application with data, we also built an I/O interface that first mimicked Node’s conventional use of callbacks and later went through a detailed refactoring exercise to convert it to use promises. Promises offer an elegant way of working with asynchronous interfaces, and makes concurrency a lot easier, even when we need to work with results in a predictable order. Promises are usable in any JavaScript setting, and the Ajax tools seems particularly fit for this style of interface. In the next chapter we will use the tools built in Chapter 12, Abstracting Browser Differences: Ajax, and Chapter 13, Streaming Data with Ajax and Comet, to build a client for the Node backend, resulting in a completely usable in-browser instant chat application. From the Library of WoweBook.Com Download from www.eBookTM.com ptg This page intentionally left blank From the Library of WoweBook.Com Download from www.eBookTM.com ptg 15 TDD and DOM Manipulation: The Chat Client D eveloping client-side JavaScript includes a fair amount of DOM manipulation. In this chapter we will use test-driven development to implement a client for the chat backend we developed in Chapter 14, Server-Side JavaScript with Node.js. By doing so we will see how to apply the techniques we have learned so far to test DOM manipulation and event handling. The DOM is an API just like any other, which means that testing it should be fairly straightforward so long as we adhere to the single responsibility principle and keep components loosely coupled. The DOM does potentially present a challenge in that it consists entirely of host objects, but as we will see, we can work around the potential problems. 15.1 Planning the Client The task at hand is building a simple chat GUI. The resulting application will have two views: when the user enters the application she will be presented with a form in which to enter the desired username. Submitting the form will remove it and display a list of messages and a form to enter new ones in its place. As usual, we will keep the scope at a minimum to get through the entire exercise, so for example, there will be no cookie management to remember the user. Throughout the chapter ideas on how to add more features to the client will be suggested as exercises. 389 From the Library of WoweBook.Com Download from www.eBookTM.com ptg 390 TDD and DOM Manipulation: The Chat Client 15.1.1 Directory Structure Again, we will use JsTestDriver to run the tests. The client will eventually use all the code developed throughout Part III, Real-World Test-Driven Development in JavaScript, but we will start with a bare minimum and add in dependencies as they are required. For the TDD session, some of the dependencies will always be stubbed, meaning we won’t need them to develop the client. Listing 15.1 shows the initial directory structure. Listing 15.1 Initial directory structure chris@laptop:~/projects/chat_client$ tree . | jsTestDriver.conf | lib | | stub.js | ` tdd.js | src ` test stub.js contains the stubFn function from Chapter 13, Streaming Data with Ajax and Comet, and tdd.js contains the tddjs object along with the various tools built in Part II, JavaScript for Programmers, Listing 15.2 shows the contents of the jsTestDriver.conf configuration file. As usual, you can download the initial project state from the book’s website. 1 Listing 15.2 The initial JsTestDriver configuration server: http://localhost:4224 load: - lib/*.js - src/*.js - test/*.js 15.1.2 Choosing the Approach Prior to launching the TDD session we need a general idea on how we’re going to build the client. Our main priorities are to keep a clear separation between the DOM and the data (provided by cometClient from Chapter 13, Streaming Data 1. http://tddjs.com From the Library of WoweBook.Com Download from www.eBookTM.com ptg 15.1 Planning the Client 391 with Ajax and Comet) and to control all dependencies from the outside, i.e., using dependency injection. To achieve this we will employ a derivative oftheModel-View- Controller (MVC) design pattern frequently referred to as Model-View-Presenter (MVP), which is very well suited to facilitate unit testing and fits well with test-driven development. 15.1.2.1 Passive View MVP is practiced in a variety of ways and we will apply it in a manner that leans to- ward what Martin Fowler, renowned programmer, author, and thinker, calls Passive View. In this model, the view notifies the presenter—controller in Passive View— of user input, and the controller completely controls the state of the view. The controller responds to events in the view and manipulates the underlying model. In a browser setting, the DOM is the view. For the chat application the model will be provided by the cometClient object, and our main task is to develop the controllers. Note the plural form; there are many controllers, each discrete widget or even widget component can be represented by its own view and controller, sometimes referred to as an MVP axis. This makes it easy to adhere to the single responsibility principle, in which each object has one well-defined task. Throughout this chapter we will refer to a controller/view duo as a component. We will divide the chat client into three distinct components: the user form, the message list, and the message form. The message list and form will not be displayed until the user form is successfully completed. However, this flow will be controlled from the outside, as controllers will not be aware of other controllers. Keeping them completely decoupled means we can more easily manipulate the client by adding or removing components, and it makes them easier to test. 15.1.2.2 Displaying the Client We need some DOM elements to display the components. To keep the scope man- ageable within the confines of a single chapter, we’re going to manually write the required markup in the HTML file that serves the application. The client is not going to make any sense to users without JavaScript, or without a sufficiently capable JavaScript engine. To avoid presenting the user with controls they cannot meaningfully use, we will initially hide all the chat related markup, and have the individual controllers append the “js-chat” class name to the various ele- ments used by the client. This way we can use CSS to display elements as JavaScript enhances them. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 392 TDD and DOM Manipulation: The Chat Client 15.2 The User Form The user form is in charge of collecting the user’s desired chat name. As the server currently has no concept of connected users, it does not need to validate the user name in any way, i.e., two users may be online at the same time using the same name. The controller requires a DOM form element as its view, and expects this to contain at least one text input, from which it will read the username when the form is submitted. When the form is submitted, the controller will assign the user to a property of the model object, to make it available to the rest of the application. Then it will emit an event, allowing other parts of the application to act on the newly arrived user. 15.2.1 Setting the View The first task is to set the view, i.e., assign the DOM element that is the visual representation of the component. 15.2.1.1 Setting Up the Test Case We start by setting up the test case and adding the first test, which expects user- FormController to be an object. Listing 15.3 shows the initial test case. Save it in test/user _ form _ controller _ test.js. Listing 15.3 Expecting the object to exist (function () { var userController = tddjs.chat.userFormController; TestCase("UserFormControllerTest", { "test should be object": function () { assertObject(userController); } }); }()); Listing 15.4 passes the test by setting up the userFormController object. Save the listing in src/user _ form _ controller.js. Listing 15.4 Defining the controller tddjs.namespace("chat").userFormController = {}; From the Library of WoweBook.Com Download from www.eBookTM.com . derivative oftheModel-View- Controller (MVC) design pattern frequently referred to as Model-View-Presenter (MVP), which is very well suited to facilitate unit testing and fits well with test-driven development. 15.1.2.1. client-side JavaScript includes a fair amount of DOM manipulation. In this chapter we will use test-driven development to implement a client for the chat backend we developed in Chapter 14, Server-Side. tests. The client will eventually use all the code developed throughout Part III, Real-World Test-Driven Development in JavaScript, but we will start with a bare minimum and add in dependencies

Ngày đăng: 04/07/2014, 22:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan