├── README.md ├── ch1 └── enerjy.pdf ├── ch2 ├── pg14 ├── pg15 ├── pg15.1 ├── pg16 ├── pg16.1 ├── pg17 ├── pg17.1 ├── pg18 ├── pg18.1 ├── pg19 ├── pg19.1 ├── pg31 ├── pg32 ├── pg32.1 ├── pg33 ├── pg34 ├── pg35 ├── pg36 ├── pg37 ├── pg37.1 ├── pg43 ├── pg43.1 ├── pg44 ├── pg46 ├── pg46.1 ├── pg46.2 ├── pg47 ├── pg49 ├── pg51 └── pg53 ├── ch3 ├── pg61 ├── pg62 ├── pg62.1 ├── pg63 ├── pg64 ├── pg64.1 ├── pg64.2 ├── pg66 ├── pg66.1 ├── pg69 ├── pg70 ├── pg71 ├── pg72 ├── pg73 ├── pg73.1 ├── pg75 ├── pg75.1 ├── pg76 ├── pg77 ├── pg77.1 └── pg78 ├── ch4 ├── pg100 ├── pg102 ├── pg103 ├── pg103.1 ├── pg104 ├── pg105 ├── pg109 ├── pg110 ├── pg111 ├── pg112 ├── pg112.1 ├── pg85 ├── pg85.1 ├── pg91 ├── pg92 ├── pg92.1 ├── pg93 ├── pg94 ├── pg94.1 ├── pg96 ├── pg96.1 ├── pg97 ├── pg98 └── pg99 ├── ch5 ├── pg115 ├── pg116 ├── pg117 ├── pg117.1 ├── pg121 ├── pg122 ├── pg123 ├── pg124 ├── pg125 ├── pg126 ├── pg127 ├── pg130 ├── pg131 └── pg132 ├── ch6 ├── pg140 ├── pg140.1 ├── pg142 ├── pg144 ├── pg145 ├── pg146 ├── pg149 ├── pg149.1 ├── pg151 ├── pg161 ├── pg161.1 ├── pg161.2 ├── pg162 └── pg163 ├── ch7 ├── pg179 ├── pg180 ├── pg188 ├── pg190 ├── pg191 ├── pg192 └── pg196 └── ch8 ├── Makefile ├── hudson_jslint.pl ├── pg224 └── pg225 /README.md: -------------------------------------------------------------------------------- 1 | Code Examples and Snippets from [Testable JavaScript](http://shop.oreilly.com/product/0636920024699.do) 2 | -------------------------------------------------------------------------------- /ch1/enerjy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzo/TestableJS/cc8a0172c0812c7a8d226952aab2a1c93264ccf0/ch1/enerjy.pdf -------------------------------------------------------------------------------- /ch2/pg14: -------------------------------------------------------------------------------- 1 | function configure(values) { 2 | var fs = require('fs') 3 | , config = { docRoot: '/somewhere' } 4 | , key 5 | , stat 6 | ; 7 | for (key in values) { 8 | config[key] = values[key]; 9 | } 10 | try { 11 | stat = fs.statSync(config.docRoot); 12 | if (!stat.isDirectory()) { 13 | throw new Error('Is not valid'); 14 | } 15 | } catch(e) { 16 | console.log("** " + config.docRoot + 17 | " does not exist or is not a directory!! **"); 18 | return; 19 | } 20 | // ... check other values ... 21 | return config; 22 | } 23 | -------------------------------------------------------------------------------- /ch2/pg15: -------------------------------------------------------------------------------- 1 | describe("configure tests", function() { 2 | it("undef if docRoot does not exist", function() { 3 | expect(configure({ docRoot: '/xxx' })).toBeUndefined(); 4 | }); 5 | it("not undef if docRoot does exist", function() { 6 | expect(configure({ docRoot: '/tmp' })).not.toBeUndefined(); 7 | }); 8 | it("adds values to config hash", function() { 9 | var config = configure({ docRoot: '/tmp', zany: 'crazy' }); 10 | expect(config).not.toBeUndefined(); 11 | expect(config.zany).toEqual('crazy'); 12 | expect(config.docRoot).toEqual('/tmp'); 13 | }); 14 | it("verifies value1 good...", function() { 15 | }); 16 | it("verifies value1 bad...", function() { 17 | }); 18 | // ... 19 | }); 20 | -------------------------------------------------------------------------------- /ch2/pg15.1: -------------------------------------------------------------------------------- 1 | function configure(values) { 2 | var config = { docRoot: '/somewhere' } 3 | , key 4 | ; 5 | 6 | for (key in values) { 7 | config[key] = values[key]; 8 | } 9 | 10 | return config; 11 | } 12 | 13 | function validateDocRoot(config) { 14 | var fs = require('fs') 15 | , stat 16 | ; 17 | 18 | stat = fs.statSync(config.docRoot); 19 | if (!stat.isDirectory()) { 20 | throw new Error('Is not valid'); 21 | } 22 | } 23 | 24 | function validateSomethingElse(config) { ... } 25 | -------------------------------------------------------------------------------- /ch2/pg16: -------------------------------------------------------------------------------- 1 | describe("validate value1", function() { 2 | it("accepts the correct value", function() { 3 | // some expects 4 | }); 5 | 6 | it("rejects the incorrect value", function() { 7 | // some expects 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /ch2/pg16.1: -------------------------------------------------------------------------------- 1 | function configure(values) { 2 | var config = { docRoot: '/somewhere' }; 3 | for (var key in values) { 4 | config[key] = values[key]; 5 | } 6 | 7 | validateDocRoot(config); 8 | validateSomethingElse(config); 9 | //... 10 | return config; 11 | } 12 | -------------------------------------------------------------------------------- /ch2/pg17: -------------------------------------------------------------------------------- 1 | var fields { 2 | docRoot: { validator: validateDocRoot, default: '/somewhere' } 3 | , somethingElse: { validator: validateSomethingElse } 4 | }; 5 | 6 | function configure(values) { 7 | var config = {}; 8 | for (var key in fields) { 9 | if (typeof values[key] !== 'undefined') { 10 | fields[key].validator(values[key]); 11 | config[key] = values[key]; 12 | } else { 13 | config[key] = fields[key].default; 14 | } 15 | } 16 | return config; 17 | } 18 | -------------------------------------------------------------------------------- /ch2/pg17.1: -------------------------------------------------------------------------------- 1 | var obj = { realRoot : '/somewhere' }; 2 | Object.defineProperty(obj, 'docRoot', { 3 | enumerable: true 4 | , set: function(value) { 5 | validateDocRoot(value); this.realRoot = value; } 6 | } 7 | ); 8 | -------------------------------------------------------------------------------- /ch2/pg18: -------------------------------------------------------------------------------- 1 | var Obj = (function() { 2 | return function() { 3 | var docRoot = '/somewhere'; 4 | this.validateDocRoot = function(val) { 5 | // validation logic - throw Error if not OK 6 | }; 7 | 8 | this.setDocRoot = function(val) { 9 | this.validateDocRoot(val); 10 | docRoot = val; 11 | }; 12 | 13 | this.getDocRoot = function() { 14 | return docRoot; 15 | }; 16 | }; 17 | }()); 18 | -------------------------------------------------------------------------------- /ch2/pg18.1: -------------------------------------------------------------------------------- 1 | var myObject = new Obj(); 2 | try { 3 | myObject.setDocRoot('/somewhere/else'); 4 | } catch(e) { 5 | // something wrong with my new doc root 6 | // old value of docRoot still there 7 | } 8 | // all is OK 9 | console.log(myObject.getDocRoot()); 10 | -------------------------------------------------------------------------------- /ch2/pg19: -------------------------------------------------------------------------------- 1 | var Obj = (function() { 2 | return function() { 3 | var docRoot = '/somewhere'; 4 | this.validateDocRoot = function(val) { 5 | // validation logic - throw Exception if not OK 6 | }; 7 | 8 | this.setDocRoot = function(val) { 9 | this.validateDocRoot(val); 10 | docRoot = val; 11 | }; 12 | 13 | this.getDocRoot = function() { 14 | return docRoot; 15 | }; 16 | Object.preventExtensions(this) 17 | }; 18 | }()); 19 | -------------------------------------------------------------------------------- /ch2/pg19.1: -------------------------------------------------------------------------------- 1 | describe("validate docRoot", function() { 2 | var config = new Obj(); 3 | it("throw if docRoot does not exist", function() { 4 | expect(config.validateDocRoot.bind(config, '/xxx')).toThrow(); 5 | }); 6 | it("not throw if docRoot does exist", function() { 7 | expect(config.validateDocRoot.bind(config, '/tmp')).not.toThrow(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /ch2/pg31: -------------------------------------------------------------------------------- 1 | YUI.use('myModule', function(Y) { 2 | var myModule = function() { 3 | this.a = new Y.A(); 4 | this.b = new Y.B(); 5 | this.c = new Y.C(); 6 | }; 7 | 8 | Y.MyModule = myModule; 9 | }, { requires: [ 'a', 'b', 'c' ] }); 10 | -------------------------------------------------------------------------------- /ch2/pg32: -------------------------------------------------------------------------------- 1 | YUI.use('myModule', function(Y) { 2 | var myModule = function() { 3 | this.a = new Y.A(); 4 | this.b = new Y.B(); 5 | this.c = new Y.C(); 6 | this.d = new Y.D(); 7 | this.e = new Y.E(); 8 | this.f = new Y.F(); 9 | this.g = new Y.G(); 10 | this.h = new Y.H(); 11 | }; 12 | Y.MyModule = myModule; 13 | }, { requires: [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' ] }); 14 | -------------------------------------------------------------------------------- /ch2/pg32.1: -------------------------------------------------------------------------------- 1 | YUI.use('mySubModule', function(Y) { 2 | var mySubModule = function() { 3 | this.a = new Y.A(); 4 | this.b = new Y.B(); 5 | this.c = new Y.C(); 6 | this.d = new Y.D(); 7 | }; 8 | 9 | mySubModule.prototype.getA = function() { return this.a; }; 10 | mySubModule.prototype.getB = function() { return this.b; }; 11 | mySubModule.prototype.getC = function() { return this.c; }; 12 | mySubModule.prototype.getD = function() { return this.d; }; 13 | Y.MySubModule = mySubModule; 14 | }, { requires: [ 'a', 'b', 'c', 'd'] }); 15 | 16 | YUI.use('myModule', function(Y) { 17 | var myModule = function() { 18 | var sub = new Y.MySubModule(); 19 | this.a = sub.getA(); 20 | this.b = sub.getB(); 21 | this.c = sub.getC(); 22 | this.d = sub.getD(); 23 | this.e = new Y.E(); 24 | this.f = new Y.F(); 25 | this.g = new Y.G(); 26 | this.h = new Y.H(); 27 | }; 28 | Y.MyModule = myModule; 29 | }, { requires: [ 'mySubModule', 'e', 'f', 'g', 'h' ] }); 30 | -------------------------------------------------------------------------------- /ch2/pg33: -------------------------------------------------------------------------------- 1 | function makeChickenDinner(ingredients) { 2 | var chicken = new ChickenBreast() 3 | , oven = new ConventionalOven() 4 | , mixer = new Mixer() 5 | , dish = mixer.mix(chicken, ingredients) 6 | ; 7 | 8 | return oven.bake(dish, new FDegrees(350), new Timer("50 minutes")); 9 | } 10 | var dinner = makeChickenDinner(ingredients); 11 | -------------------------------------------------------------------------------- /ch2/pg34: -------------------------------------------------------------------------------- 1 | describe("test make dinner", function() { 2 | // Mocks 3 | Food = function(obj) {}; 4 | Food.prototype.attr = {}; 5 | MixedFood = function(args) { 6 | var obj = Object.create(Food.prototype); 7 | obj.attr.isMixed = true; return obj; 8 | }; 9 | 10 | CookedFood = function(dish) { 11 | var obj = Object.create(Food.prototype); 12 | obj.attr.isCooked = true; return obj; 13 | }; 14 | 15 | FDegrees = function(temp) { this.temp = temp }; 16 | Meal = function(dish) { this.dish = dish }; 17 | Timer = function(timeSpec) { this.timeSpec = timeSpec; }; 18 | ChickenBreast = function() { 19 | var obj = Object.create(Food.prototype); 20 | obj.attr.isChicken = true; return obj; 21 | }; 22 | ConventionalOven = function() { 23 | this.bake = function(dish, degrees, timer) { 24 | return new CookedFood(dish, degrees, timer); 25 | }; 26 | }; 27 | Mixer = function() { 28 | this.mix = function(chicken, ingredients) { 29 | return new MixedFood(chicken, ingredients); 30 | }; 31 | }; 32 | Ingredients = function(ings) { this.ings = ings; }; 33 | // end Mocks 34 | 35 | it("cooked dinner", function() { 36 | this.addMatchers({ 37 | toBeYummy: function(expected) { 38 | return this.actual.attr.isCooked && this.actual.attr.isMixed; 39 | } 40 | }); 41 | 42 | var ingredients = new Ingredients('parsley', 'salt') 43 | , dinner = makeChickenDinner(ingredients) 44 | ; 45 | expect(dinner).toBeYummy(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /ch2/pg35: -------------------------------------------------------------------------------- 1 | function Cooker(oven) { 2 | this.oven = oven; 3 | } 4 | 5 | Cooker.prototype.bake = function(dish, deg, timer) { 6 | return this.oven.bake(dish, deg, timer); 7 | }; 8 | 9 | Cooker.prototype.degrees_f = function(deg) { 10 | return new FDegrees(deg); 11 | }; 12 | 13 | Cooker.prototype.timer = function(time) { 14 | return new Timer(time); 15 | }; 16 | 17 | function makeChickenDinner(ingredients, cooker) { 18 | var chicken = new ChickenBreast() 19 | , mixer = new Mixer() 20 | , dish = mixer.mix(chicken, ingredients) 21 | ; 22 | return cooker.bake(dish , cooker.degrees_f(350) , cooker.timer("50 minutes")); 23 | } 24 | 25 | var cooker = new Cooker(new ConventionalOven()) 26 | , dinner = makeChickenDinner(ingredients, cooker); 27 | -------------------------------------------------------------------------------- /ch2/pg36: -------------------------------------------------------------------------------- 1 | describe("test make dinner refactored", function() { 2 | // Mocks 3 | Food = function() {}; 4 | Food.prototype.attr = {}; 5 | MixedFood = function(args) { 6 | var obj = Object.create(Food.prototype); 7 | obj.attr.isMixed = true; 8 | return obj; 9 | }; 10 | CookedFood = function(dish) { 11 | var obj = Object.create(Food.prototype); 12 | obj.attr.isCooked = true; 13 | return obj; 14 | }; 15 | ChickenBreast = function() { 16 | var obj = Object.create(Food.prototype); 17 | obj.attr.isChicken = true; 18 | return obj; 19 | }; 20 | Meal = function(dish) { this.dish = dish }; 21 | Mixer = function() { 22 | this.mix = function(chicken, ingredients) { 23 | return new MixedFood(chicken, ingredients); 24 | }; 25 | }; 26 | Ingredients = function(ings) { this.ings = ings; }; 27 | // end Mocks 28 | 29 | it("cooked dinner", function() { 30 | this.addMatchers({ 31 | toBeYummy: function(expected) { 32 | return this.actual.attr.isCooked && this.actual.attr.isMixed; 33 | } 34 | }); 35 | var ingredients = new Ingredients('parsley', 'salt') 36 | , MockedCooker = function() {}; 37 | // Local (to this test) mocked Cooker object that can actually 38 | // do testing! 39 | MockedCooker.prototype = { 40 | bake: function(food, deg, timer) { 41 | expect(food.attr.isMixed).toBeTruthy(); 42 | food.attr.isCooked = true; 43 | return food 44 | } 45 | , degrees_f: function(temp) { expect(temp).toEqual(350); } 46 | , timer: function(time) { 47 | expect(time).toEqual('50 minutes'); 48 | } 49 | }; 50 | var cooker = new MockedCooker() 51 | , dinner = makeChickenDinner(ingredients, cooker) 52 | ; 53 | expect(dinner).toBeYummy(); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /ch2/pg37: -------------------------------------------------------------------------------- 1 | function makeChickenDinner(ingredients, cooker, chicken, mixer) { 2 | var dish = mixer.mix(chicken, ingredients); 3 | return cooker.bake(dish 4 | , cooker.degrees_f(350) 5 | , cooker.timer('50 minutes') 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /ch2/pg37.1: -------------------------------------------------------------------------------- 1 | describe("test make dinner injected", function() { 2 | it("cooked dinner", function() { 3 | this.addMatchers({ 4 | toBeYummy: function(expected) { 5 | return this.actual.attr.isCooked && this.actual.attr.isMixed; 6 | } 7 | }); 8 | 9 | var ingredients = ['parsley', 'salt'] 10 | , chicken = {} 11 | , mixer = { 12 | mix: function(chick, ings) { 13 | expect(ingredients).toBe(ings); 14 | expect(chicken).toBe(chick); 15 | return { attr: { isMixed: true } }; 16 | } 17 | } 18 | , MockedCooker = function() {} 19 | ; 20 | MockedCooker.prototype = { 21 | bake: function(food, deg, timer) { 22 | expect(food.attr.isMixed).toBeTruthy(); 23 | food.attr.isCooked = true; 24 | return food 25 | } 26 | , degrees_f: function(temp) { expect(temp).toEqual(350); } 27 | , timer: function(time) { 28 | expect(time).toEqual('50 minutes'); 29 | } 30 | }; 31 | var cooker = new MockedCooker() 32 | , dinner = makeChickenDinner(ingredients, cooker , chicken, mixer) 33 | ; 34 | expect(dinner).toBeYummy(); 35 | }) 36 | }); 37 | -------------------------------------------------------------------------------- /ch2/pg43: -------------------------------------------------------------------------------- 1 | function dinnerParty(guests) { 2 | var table = new Table() 3 | , invitations = new Invitations() 4 | , food = new Ingredients() 5 | , chef = new Chef() 6 | , staff = new Staff() 7 | , cloth = new FancyTableClothWithFringes() 8 | , dishes = new ChinaWithBlueBorders() 9 | , dinner 10 | ; 11 | 12 | invitations.invite(guests); 13 | table.setTable(cloth, dishes); 14 | dinner = chef.cook(ingredients); 15 | staff.serve(dinner); 16 | } 17 | -------------------------------------------------------------------------------- /ch2/pg43.1: -------------------------------------------------------------------------------- 1 | var TableClothFactory = { 2 | getTableCloth: function(color) { 3 | return Object.create(TableCloth, { color: { value: color }}); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /ch2/pg44: -------------------------------------------------------------------------------- 1 | var AbstractTableClothFactory = { 2 | getFactory: function(kind) { 3 | if (kind !== 'TEST') { 4 | return TableClothFactory; 5 | } else { 6 | return TableClothTestFactory; 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /ch2/pg46: -------------------------------------------------------------------------------- 1 | var SpaceShuttle = function() { 2 | this.mainEngine = new SpaceShuttleMainEngine(); 3 | this.boosterEngine1 = new SpaceShuttleSolidRocketBooster(); 4 | this.boosterEngine2 = new SpaceShuttleSolidRocketBooster(); 5 | this.arm = new ShuttleRemoteManipulatorSystem(); 6 | }; 7 | -------------------------------------------------------------------------------- /ch2/pg46.1: -------------------------------------------------------------------------------- 1 | var SpaceShuttle = function(mainEngine, b1, b2, arm) { 2 | this.mainEngine = mainEngine; 3 | this.boosterEngine1 = b1; 4 | this.boosterEngine2 = b2; 5 | this.arm = arm; 6 | }; 7 | -------------------------------------------------------------------------------- /ch2/pg46.2: -------------------------------------------------------------------------------- 1 | knit = require('knit'); 2 | knit.config(function (bind) { 3 | bind('MainEngine').to(SpaceShuttleMainEngine).is("construtor"); 4 | bind('BoosterEngine1').to(SpaceShuttleSolidRocketBooster).is("constructor"); 5 | bind('BoosterEngine2').to(SpaceShuttleSolidRocketBooster).is("constructor"); 6 | bind('Arm').to(ShuttleRemoteManipulatorSystem).is("constructor"); 7 | bind('ShuttleDiscovery').to(SpaceShuttle).is("constructor"); 8 | bind('ShuttleEndeavor').to(SpaceShuttle).is("constructor"); 9 | bind('Pad').to(new LaunchPad()).is("singleton"); 10 | }); 11 | -------------------------------------------------------------------------------- /ch2/pg47: -------------------------------------------------------------------------------- 1 | knit.inject(function(ShuttleDiscovery, ShuttleEndeavor, Pad) { 2 | ShuttleDiscovery.blastOff(Pad); 3 | ShuttleEndeavor.blastOff(Pad); 4 | }); 5 | -------------------------------------------------------------------------------- /ch2/pg49: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides some mathematical functions 3 | * 4 | * @class Math 5 | **/ 6 | /** 7 | * This function accepts two operands, 'a' and 'b' and returns 8 | * their sum (or concatenation if they are strings) 9 | * 10 | * @method sum 11 | * @param {Number or String} a first operand 12 | * @param {Number or String} b second operand 13 | * @return {Number or String} The 'summed' value 14 | */ 15 | exports.sum = function(a, b) { return a + b }; 16 | /** 17 | * This function accepts two operands, 'a' and 'b' and returns 18 | * their product 19 | * 20 | * @method product 21 | * @param {Number} a first operand 22 | * @param {Number} b second operand 23 | * @return {Number} The product 24 | */ 25 | exports.mult = function(a, b) { return a * b }; 26 | -------------------------------------------------------------------------------- /ch2/pg51: -------------------------------------------------------------------------------- 1 | /** 2 | * This function accepts two operands, 'a' and 'b' and returns 3 | * their sum (or concatenation if they are strings) 4 | * 5 | * @name sum 6 | * @function 7 | * @param {Number or String} a first operand 8 | * @param {Number or String} b second operand 9 | * @returns {Number or String} The 'summed' value 10 | */ 11 | exports.sum = function(a, b) { return a + b }; 12 | /** 13 | * This function accepts two operands, 'a' and 'b' and returns 14 | * their product 15 | * 16 | * @name product 17 | * @function 18 | * @param {Number} a first operand 19 | * @param {Number} b second operand 20 | * @returns {Number} The product 21 | */ 22 | exports.mult = function(a, b) { return a * b }; 23 | -------------------------------------------------------------------------------- /ch2/pg53: -------------------------------------------------------------------------------- 1 | /** 2 | * This function accepts two operands, 'a' and 'b' and returns their 3 | * sum (or concatenation if they are strings) 4 | */ 5 | exports.sum = function(a, b) { return a + b }; 6 | /** 7 | * This function accepts two operands, 'a' and 'b' and 8 | * returns their product 9 | */ 10 | exports.mult = function(a, b) { return a * b }; 11 | -------------------------------------------------------------------------------- /ch3/pg61: -------------------------------------------------------------------------------- 1 | YUI().add('login', function(Y) { 2 | Y.one('#submitButton').on('click', logIn); 3 | function logIn(e) { 4 | var username = Y.one('#username').get('value') 5 | , password = Y.one('#password').get('value') 6 | , cfg = { 7 | data: JSON.stringify({username: username, password: password }) 8 | , method: 'POST' 9 | , on: { 10 | complete: function(tid, resp, args) { 11 | if (resp.status === 200) { 12 | var response = JSON.parse(resp.responseText); 13 | if (response.loginOk) { 14 | userLoggedIn(username); 15 | } else { 16 | failedLogin(username); 17 | } 18 | } else { 19 | networkError(resp); 20 | } 21 | } 22 | } 23 | } 24 | , request = Y.io('/login', cfg) 25 | ; 26 | } 27 | }, '1.0', { requires: [ 'node', 'io-base' ] }); 28 | -------------------------------------------------------------------------------- /ch3/pg62: -------------------------------------------------------------------------------- 1 | YUI().add('login', function(Y) { 2 | Y.one('#submitButton').on('click', logIn); 3 | function logIn(e) { 4 | var username = Y.one('#username').get('value') 5 | , password = Y.one('#password').get('value') 6 | ; 7 | 8 | eventHub.fire('logIn' 9 | , { username: username, password: password } 10 | , function(err, resp) { 11 | if (!err) { 12 | if (resp.loginOk) { 13 | userLoggedIn(username); 14 | } else { 15 | failedLogin(username); 16 | } 17 | } else { 18 | networkError(err); 19 | } 20 | }); 21 | } 22 | }, '1.0', { requires: [ 'node', 'EventHub' ] }); 23 | -------------------------------------------------------------------------------- /ch3/pg62.1: -------------------------------------------------------------------------------- 1 | YUI().use('test', 'console', 'node-event-simulate' , 'login', function(Y) { 2 | // Factory for mocking Y.io 3 | var getFakeIO = function(args) { 4 | return function(url, config) { 5 | Y.Assert.areEqual(url, args.url); 6 | Y.Assert.areEqual(config.data, args.data); 7 | Y.Assert.areEqual(config.method, args.method); 8 | Y.Assert.isFunction(config.on.complete); 9 | config.on.complete(1, args.responseArg); 10 | }; 11 | } 12 | , realIO = Y.io 13 | ; 14 | -------------------------------------------------------------------------------- /ch3/pg63: -------------------------------------------------------------------------------- 1 | var testCase = new Y.Test.Case({ 2 | name: "test ajax login" 3 | , tearDown: function() { 4 | Y.io = realIO; 5 | } 6 | , testLogin : function () { 7 | var username = 'mark' 8 | , password = 'rox' 9 | ; 10 | 11 | Y.io = getFakeIO({ 12 | url: '/login' 13 | , data: JSON.stringify({ 14 | username: username 15 | , password: password 16 | }) 17 | , method: 'POST' 18 | , responseArg: { 19 | status: 200 20 | , responseText: JSON.stringify({ loginOk: true }) 21 | } 22 | }); 23 | 24 | userLoggedIn = function(user) { 25 | Y.Assert.areEqual(user, username); 26 | }; 27 | 28 | failedLogin = function() { 29 | Y.Assert.fail('login should have succeeded!'); 30 | }; 31 | 32 | networkError = function() { 33 | Y.Assert.fail('login should have succeeded!'); 34 | }; 35 | 36 | Y.one('#username').set('value', username); 37 | Y.one('#password').set('value', password); 38 | Y.one('#submitButton').simulate('click'); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /ch3/pg64: -------------------------------------------------------------------------------- 1 | var suite = new Y.Test.Suite('login'); 2 | suite.add(testCase); 3 | Y.Test.Runner.add(suite); 4 | //Initialize the console 5 | new Y.Console({ 6 | newestOnTop: false 7 | }).render('#log'); 8 | Y.Test.Runner.run(); 9 | -------------------------------------------------------------------------------- /ch3/pg64.1: -------------------------------------------------------------------------------- 1 | YUI().use('test', 'console', 'node-event-simulate' , 'login', function(Y) { 2 | 3 | // Factory for mocking EH 4 | var getFakeEH = function(args) { 5 | return { 6 | fire: function(event, eventArgs, cb) { 7 | Y.Assert.areEqual(event, args.event); 8 | Y.Assert.areEqual(JSON.stringify(eventArgs), 9 | JSON.stringify(args.data)); 10 | Y.Assert.isFunction(cb); 11 | cb(args.err, args.responseArg); 12 | } 13 | }; 14 | }; 15 | }); 16 | -------------------------------------------------------------------------------- /ch3/pg64.2: -------------------------------------------------------------------------------- 1 | var testCase = new Y.Test.Case({ 2 | name: "test eh login" 3 | , testLogin : function () { 4 | var username = 'mark' 5 | , password = 'rox' 6 | ; 7 | 8 | eventHub = getFakeEH({ 9 | event: 'logIn' 10 | , data: { 11 | username: username 12 | , password: password 13 | } 14 | , responseArg: { loginOk: true } 15 | }); 16 | 17 | userLoggedIn = function(user) { 18 | Y.Assert.areEqual(user, username); 19 | }; 20 | 21 | failedLogin = function() { 22 | Y.Assert.fail('login should have succeeded!'); 23 | }; 24 | 25 | networkError = function() { 26 | Y.Assert.fail('login should have succeeded!'); 27 | }; 28 | 29 | Y.one('#username').set('value', username); 30 | Y.one('#password').set('value', password); 31 | Y.one('#submitButton').simulate('click'); 32 | } 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /ch3/pg66: -------------------------------------------------------------------------------- 1 | // somewhere on server 2 | eventHub.on('REGISTER_USER', function(obj) { 3 | // stick obj.name into user DB 4 | eventHub.fire('USER_REGISTERED', { name: obj.name }); 5 | }); 6 | // somewhere global 7 | eventHub.on('USER_REGISTERED', function(user) { 8 | console.log(user.name + ' registered!'); 9 | }); 10 | // somewhere specific 11 | eventHub.fire('REGISTER_USER', { name: 'mark' }); 12 | -------------------------------------------------------------------------------- /ch3/pg66.1: -------------------------------------------------------------------------------- 1 | // Somewhere on the server 2 | eventHub.on('ADD_TO_CART' 3 | , function(userId, itemId, callback) { 4 | d.cart.push(itemId); 5 | callback(null, { items: userId.cart.length }); 6 | } 7 | ); 8 | 9 | // Meanwhile, somewhere else (in the browser, probably) 10 | function addItemToCart(userId, itemId) { 11 | eventHub.fire('ADD_TO_CART' 12 | , { user_id: userId, item_id: itemId } 13 | , function(err, result) { 14 | if (!err) { 15 | console.log('Cart now has: ' + result.cart.items + ' items'); 16 | } 17 | } 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /ch3/pg69: -------------------------------------------------------------------------------- 1 | // Some handle to a datastore 2 | function DB(eventHub, dbHandle) { 3 | // Add user function 4 | eventHub.on('CREATE_USER', createAddUserHandler(eventHub, dbHandle)); 5 | } 6 | 7 | function createAddUserHandler(eventHub, dbHandle) { 8 | return function addUser(user) { 9 | var result = dbHandle.addRow('user', user); 10 | eventHub.fire('USER_CREATED', { 11 | success: result.success 12 | , message: result.message 13 | , user: user 14 | }); 15 | } 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /ch3/pg70: -------------------------------------------------------------------------------- 1 | YUI().use('test', function(Y) { 2 | var eventHub = Y.Mock() 3 | , addUserTests = new Y.Test.Case({ 4 | name: 'add user' 5 | , testAddOne: function() { 6 | var user = { user_id: 'mark' } 7 | , dbHandle = { // DB stub 8 | addRow: function(user) { 9 | return { user: user 10 | , success: true 11 | , message: 'ok' }; 12 | } 13 | } 14 | , addUser = createAddUserHandler(eventHub, dbHandle) 15 | ; 16 | 17 | Y.Mock.expect( 18 | eventHub 19 | , 'fire' 20 | , [ 21 | 'USER_CREATED' 22 | , { success: true, message: 'ok', user: user } 23 | ] 24 | ); 25 | DB(eventHub, dbHandle); // Inject test versions 26 | addUser(user); 27 | Y.Mock.verify(eventHub); 28 | } 29 | }); 30 | 31 | eventHub.on = function(event, func) {}; 32 | eventHub.fire = function(event, data) {}; 33 | Y.Test.Runner.add(addUserTests); 34 | Y.Test.Runner.run(); 35 | }); 36 | -------------------------------------------------------------------------------- /ch3/pg71: -------------------------------------------------------------------------------- 1 | var DB = function(dbHandle) { 2 | this.handle = dbHandle; 3 | }; 4 | 5 | DB.prototype.addUser = function(user) { 6 | var result = dbHandle.addRow('user', user); 7 | return { 8 | success: result.success 9 | , message: result.message 10 | , user: user 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /ch3/pg72: -------------------------------------------------------------------------------- 1 | YUI().use('test', function(Y) { 2 | var addUserTests = new Y.Test.Case({ 3 | name: 'add user' 4 | , addOne: function() { 5 | var user = { user_id: 'mark' } 6 | , dbHandle = { // DB stub 7 | addRow: function(user) { 8 | return { user: user 9 | , success: true 10 | , message: 'ok' }; 11 | } 12 | } 13 | , result 14 | ; 15 | 16 | DB(dbHandle); // Inject test versions 17 | result = addUser(user); 18 | Y.Assert.areSame(result.user, user.user); 19 | Y.Assert.isTrue(result.success); 20 | Y.Assert.areSame(result.message, 'ok'); 21 | } 22 | }); 23 | 24 | Y.Test.Runner.add(addUserTests); 25 | Y.Test.Runner.run(); 26 | }); 27 | -------------------------------------------------------------------------------- /ch3/pg73: -------------------------------------------------------------------------------- 1 | // Return an object with 'on' and 'fire' functions for the specified 2 | // eventName 3 | hub.addEvent = function(eventName) { 4 | var _this = this; 5 | this.events[eventName] = { 6 | on: function(callback) { 7 | _this.on.call(_this, eventName, callback); 8 | } 9 | , fire: function() { 10 | Array.prototype.unshift.call(arguments, eventName); 11 | this.fire.apply(_this, arguments); 12 | } 13 | }; 14 | return this.events[eventName]; 15 | }; 16 | -------------------------------------------------------------------------------- /ch3/pg73.1: -------------------------------------------------------------------------------- 1 | var clickEvent = hub.addEvent('click'); 2 | clickEvent.on(function(data) { /* got a click event! */ }); 3 | clickEvent.fire({ button: 'clicked' }); // fired a click event! 4 | -------------------------------------------------------------------------------- /ch3/pg75: -------------------------------------------------------------------------------- 1 | eventSwitch.on('depositMoney', function(data) { 2 | cash += data.depositAmount; 3 | eventSwitch.emit('depositedMoney', cash); 4 | }, { type: 'unicast' }); 5 | -------------------------------------------------------------------------------- /ch3/pg75.1: -------------------------------------------------------------------------------- 1 | eventHub.on('eventClient:done', function(event) { 2 | console.log('DONE LISTENING FOR ' + event); 3 | // finish handling any outstanding events & then safely: 4 | process.exit(0); 5 | }); 6 | -------------------------------------------------------------------------------- /ch3/pg76: -------------------------------------------------------------------------------- 1 | eventHub.on('eventClient:done', function(event) { 2 | console.log('I have been advised to stop listening for event: ' + event); 3 | eventHub.removeAllListeners(event); 4 | }); 5 | -------------------------------------------------------------------------------- /ch3/pg77: -------------------------------------------------------------------------------- 1 | var EventHub = require('EventHub/clients/server/eventClient.js'); 2 | , eventHub = EventHub.getClientHub('http://localhost:5883'); 3 | 4 | eventHub.on('ADD_USER', function(user) { 5 | // Add user logic 6 | eventHub.fire('ADD_USER_DONE', { success: true, user: user }); 7 | }); 8 | -------------------------------------------------------------------------------- /ch3/pg77.1: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | -------------------------------------------------------------------------------- /ch3/pg78: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /ch4/pg100: -------------------------------------------------------------------------------- 1 | page.onConsoleMessage = function(msg) { 2 | var obj = JSON.parse(msg); 3 | if (obj.results) { 4 | window.setTimeout(function () { 5 | console.log(obj.results); 6 | page.render('output.png'); 7 | phantom.exit(); 8 | }, 200); 9 | } else { 10 | console.log(msg); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /ch4/pg102: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User View Tests 6 | 7 | 8 |

Test User View

9 |
10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ch4/pg103: -------------------------------------------------------------------------------- 1 | var webdriverjs = require("webdriverjs") 2 | , url = '...' 3 | ; 4 | 5 | browser = webdriverjs.remote({ 6 | host: 'localhost' 7 | , port: 4444 8 | , desiredCapabilities: { browserName: 'firefox' } 9 | }); 10 | 11 | browser.init().url(url).end(); 12 | -------------------------------------------------------------------------------- /ch4/pg103.1: -------------------------------------------------------------------------------- 1 | YUI().add('selenium', function(Y) { 2 | var messages = []; 3 | , yconsole = new Y.Console() 4 | ; 5 | 6 | yconsole.on('entry', function(obj) { messages.push(obj.message); }); 7 | var TR = Y.Test.Runner; 8 | TR.subscribe(TR.COMPLETE_EVENT, function(obj) { 9 | // Data to dump 10 | var data = escape(JSON.stringify( 11 | { 12 | messages: messages 13 | , results: Y.Test.Format.JUnitXML(obj.results) 14 | } 15 | )); 16 | 17 | // Create a new Node 18 | var item = Y.Node.create('
'); 19 | item.setContent(data); 20 | // Append to document 21 | Y.one('body').append(item); 22 | }); 23 | }, '1.0', { requires: [ 'console', 'node' ] }); 24 | -------------------------------------------------------------------------------- /ch4/pg104: -------------------------------------------------------------------------------- 1 | var webdriverjs = require("webdriverjs") 2 | , url = '...' 3 | , browser = webdriverjs.remote({ 4 | host: 'localhost' 5 | , port: 4444 6 | , desiredCapabilities: { browserName: 'firefox' } 7 | }) 8 | ; 9 | 10 | browser.init().url(url).waitFor('#testresults', 10000, function(found) { 11 | var res; 12 | if (found) { 13 | res = browser.getText('#testresults', 14 | function(text) { 15 | // Do something smarter than console.log! 16 | console.log(JSON.parse(unescape(text.value))); 17 | }); 18 | } else { 19 | console.log('TEST RESULTS NOT FOUND'); 20 | } 21 | }).end(); 22 | -------------------------------------------------------------------------------- /ch4/pg105: -------------------------------------------------------------------------------- 1 | var webdriverjs = require("webdriverjs") 2 | , url 3 | , browser = webdriverjs.remote({ 4 | host: 'localhost' 5 | , port: 4444 6 | , desiredCapabilities: { browserName: 'firefox' } 7 | }) 8 | ; 9 | 10 | browser.init().url(url).waitFor('#testresults', 10000, function(found) { 11 | if (found) { 12 | var res = browser.getText('#testresults' 13 | , function(text) { 14 | // Get log messages and JUnit XML results 15 | console.log(JSON.parse(unescape(text.value))); 16 | // Now take the snapshot 17 | browser.screenshot( 18 | function(screenshot) { 19 | var fs = require('fs') 20 | , filename = 'snapshot.png' 21 | , imageData 22 | ; 23 | 24 | try { 25 | imageData = new Buffer(screenshot.value , 'base64'); 26 | fs.writeFileSync(filename, imageData); 27 | } catch(e) { 28 | console.log('Error getting snapshot: ' + e); 29 | } 30 | } 31 | ); 32 | }); 33 | } else { 34 | console.log('TEST RESULTS NOT FOUND'); 35 | } 36 | }).end(); 37 | -------------------------------------------------------------------------------- /ch4/pg109: -------------------------------------------------------------------------------- 1 | var mockery = require('mockery'); 2 | 3 | mockery.enable(); 4 | 5 | describe("Sum suite File", function() { 6 | beforeEach(function() { 7 | mockery.registerAllowable('./mySumFS', true); 8 | }); 9 | 10 | afterEach(function() { 11 | mockery.deregisterAllowable('./mySumFS'); 12 | }); 13 | 14 | it("Adds Integers!", function() { 15 | var filename = "numbers" 16 | , fsMock = { 17 | readFileSync: function (path, encoding) { 18 | expect(path).toEqual(filename); 19 | expect(encoding).toEqual('utf8'); 20 | return JSON.stringify({ a: 9, b: 3 }); 21 | } 22 | } 23 | ; 24 | 25 | mockery.registerMock('fs', fsMock); 26 | var mySum = require('./mySumFS'); 27 | expect(mySum.sum(filename)).toEqual(12); 28 | mockery.deregisterMock('fs'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /ch4/pg110: -------------------------------------------------------------------------------- 1 | it("Adds Strings!", function() { 2 | var filename = "strings" 3 | , fsMock = { 4 | readFileSync: function (path, encoding) { 5 | expect(path).toEqual(filename); 6 | expect(encoding).toEqual('utf8'); 7 | return JSON.stringify({ a: 'testable' , b: 'JavaScript' }); 8 | } 9 | } 10 | ; 11 | 12 | mockery.registerMock('fs', fsMock); 13 | var mySum = require('./mySumFS'); 14 | expect(mySum.sum(filename)).toEqual('testableJavaScript'); 15 | mockery.deregisterMock('fs'); 16 | }); 17 | -------------------------------------------------------------------------------- /ch4/pg111: -------------------------------------------------------------------------------- 1 | exports.sum = function(func, data) { 2 | var data = JSON.parse(func.apply(this, data)); 3 | return data.a + data.b; 4 | }; 5 | 6 | exports.getByFile = function(file) { 7 | var fs = require('fs'); 8 | return fs.readFileSync(file, 'utf8'); 9 | }; 10 | 11 | exports.getByParam = function(a, b) { 12 | return JSON.stringify({a: a, b: b}); 13 | }; 14 | -------------------------------------------------------------------------------- /ch4/pg112: -------------------------------------------------------------------------------- 1 | mySum = require('./mySumFunc'); 2 | describe("Sum suite Functions", function() { 3 | it("Adds By Param!", function() { 4 | var sum = mySum.sum(mySum.getByParam, [6,6]); 5 | expect(sum).toEqual(12); 6 | }); 7 | 8 | it("Adds By File!", function() { 9 | var sum = mySum.sum(mySum.getByFile, ["strings"]); 10 | expect(sum).toEqual("testableJavaScript"); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /ch4/pg112.1: -------------------------------------------------------------------------------- 1 | it("Adds By Param!", function() { 2 | 3 | var params = [ 8, 4 ] 4 | , expected = 12 5 | ; 6 | 7 | spyOn(mySum, 'getByParam').andCallThrough(); 8 | expect(mySum.sum(mySum.getByParam, params)).toEqual(expected); 9 | expect(mySum.getByParam).toHaveBeenCalled(); 10 | expect(mySum.getByParam.mostRecentCall.args).toEqual(params); 11 | }); 12 | -------------------------------------------------------------------------------- /ch4/pg85: -------------------------------------------------------------------------------- 1 | YUI({ logInclude: { TestRunner: true } }).use('test', 'test-console' , function(Y) { 2 | var testCase = new Y.Test.Case( 3 | { 4 | name: 'Sum Test' 5 | , testSimple: function () { 6 | Y.Assert.areSame(sum(2, 2), 4 , '2 + 2 does not equal 4?'); 7 | } 8 | } 9 | ); 10 | 11 | // Load it up 12 | Y.Test.Runner.add(testCase); 13 | (new Y.Test.Console({ 14 | newestOnTop: false 15 | })).render('#log'); 16 | 17 | // Run it 18 | Y.Test.Runner.run(); 19 | }); 20 | -------------------------------------------------------------------------------- /ch4/pg85.1: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sum Tests 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ch4/pg91: -------------------------------------------------------------------------------- 1 | // actual production code: 2 | function buyNowClicked(event) { 3 | hub.fire('addToShoppingCart', { item: event.target.id }); 4 | } 5 | Y.one('#products').delegate('click', buyNowClicked, '.buy-now-button'); 6 | 7 | /* ... and in your test code: */ 8 | testAddToShoppingCart: function() { 9 | var hub = Y.Mock(); 10 | Y.Mock.expect(hub, 11 | { 12 | method: "fire" 13 | , args: [ "addToShoppingCart" , Y.Mock.Value.String] 14 | } 15 | ); 16 | } 17 | Y.one('.buy-now-button').simulate('click'); 18 | Y.Mock.verify(hub); 19 | -------------------------------------------------------------------------------- /ch4/pg92: -------------------------------------------------------------------------------- 1 | function addToShoppingCart(item) { 2 | if (item.inStock()) { 3 | this.cart.push(item); 4 | return item; 5 | } else { 6 | return null; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ch4/pg92.1: -------------------------------------------------------------------------------- 1 | testAddOk: function() { 2 | var shoppingCart = new ShoppingCart() 3 | , item = { inStock: function() { return true; } } 4 | , back = shoppingCart.addToShoppingCart(item) 5 | ; 6 | 7 | Y.Assert.areSame(back, item, "Item not returned"); 8 | Y.ArrayAssert.contains(item, "Item not pushed into cart!"); 9 | } 10 | , testAddNok: function() { 11 | var shoppingCart = new ShoppingCart() 12 | , item = { inStock: function() { return false; } } 13 | , back = shoppingCart.addToShoppingCart(item) 14 | ; 15 | 16 | Y.Assert.isNull(back, "Item returned is not null"); 17 | Y.ArrayAssert.isEmpty(shoppingCart.cart, "Cart not empty!"); 18 | } 19 | -------------------------------------------------------------------------------- /ch4/pg93: -------------------------------------------------------------------------------- 1 | , testWithSpy: function() { 2 | var origSum = sum 3 | , sumSpy = function(a, b) { 4 | Y.Assert.areSame(a, 2, 'first arg is 2!'); 5 | Y.Assert.areSame(a, 9, 'second arg is 9!'); 6 | return origSum(a, b); 7 | } 8 | ; 9 | 10 | sum = sumSpy; 11 | Y.Assert.areSame(sum(2, 9), 11 , '2 + 9 does not equal 11?'); 12 | sum = origSum; // reset it (or use teardown) 13 | } 14 | -------------------------------------------------------------------------------- /ch4/pg94: -------------------------------------------------------------------------------- 1 | testAsync: function () { 2 | var test = this, myButton = Y.one('#button'); 3 | myButton.on('click', function() { 4 | this.resume(function() { 5 | Y.Assert.isTrue(true, 'You sunk my battleship!'); 6 | }); 7 | } 8 | 9 | myButton.simulate('click'); 10 | this.wait(2000); 11 | } 12 | -------------------------------------------------------------------------------- /ch4/pg94.1: -------------------------------------------------------------------------------- 1 | console = Y.Mock(); 2 | testLogHandler: function () { 3 | var sev = 'DEBUG', message = 'TEST'; 4 | Y.Mock.expect(console, { method: log 5 | , arguments: [ sev, message ] }); 6 | hub.fire('log', sev, message); 7 | 8 | this.wait(function() { 9 | Y.Mock.verify(console); 10 | }, 1000); 11 | } 12 | -------------------------------------------------------------------------------- /ch4/pg96: -------------------------------------------------------------------------------- 1 | YUI().add('phantomjs', function(Y) { 2 | var TR; 3 | if (typeof(console) !== 'undefined') { 4 | TR = Y.Test.Runner; 5 | TR.subscribe(TR.COMPLETE_EVENT, function(obj) { 6 | console.log(Y.Test.Format.JUnitXML(obj.results)); 7 | }); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /ch4/pg96.1: -------------------------------------------------------------------------------- 1 | YUI({ 2 | logInclude: { TestRunner: true }, 3 | }).use('test', 'sum', 'console', 'phantomjs', function(Y) { 4 | var suite = new Y.Test.Suite('sum'); 5 | suite.add(new Y.Test.Case({ 6 | name:'simple test', 7 | testIntAdd : function () { 8 | Y.log('testIntAdd'); 9 | Y.Assert.areEqual(Y.MySum(2 ,2), 4); 10 | } 11 | , testStringAdd : function () { 12 | Y.log('testStringAdd'); 13 | Y.Assert.areEqual(Y.MySum('my', 'sum'), 'mysum'); 14 | } 15 | })); 16 | 17 | Y.Test.Runner.add(suite); 18 | //Initialize the console 19 | var yconsole = new Y.Console({ 20 | newestOnTop: false 21 | }); 22 | yconsole.render('#log'); 23 | Y.Test.Runner.run(); 24 | }); 25 | -------------------------------------------------------------------------------- /ch4/pg97: -------------------------------------------------------------------------------- 1 | var page = new WebPage(); 2 | page.onConsoleMessage = function(msg) { 3 | console.log(msg); 4 | phantom.exit(0); 5 | }; 6 | page.open(phantom.args[0], function (status) { 7 | // Check for page load success 8 | if (status !== "success") { 9 | console.log("Unable to load file"); 10 | phantom.exit(1); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /ch4/pg98: -------------------------------------------------------------------------------- 1 | var page = new WebPage(); 2 | page.viewportSize = { width: 1024, height: 768 }; 3 | page.onConsoleMessage = function(msg) { 4 | console.log(msg); 5 | setTimeout(function() { 6 | page.render('output.png'); 7 | phantom.exit(); 8 | }, 500); 9 | }; 10 | 11 | page.open(phantom.args[0], function (status) { 12 | // Check for page load success 13 | if (status !== "success") { 14 | console.log("Unable to load file"); 15 | phantom.exit(1); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /ch4/pg99: -------------------------------------------------------------------------------- 1 | YUI().add('phantomjs', function(Y) { 2 | var yconsole = new Y.Console(); 3 | yconsole.on('entry', 4 | function(obj) { 5 | console.log(JSON.stringify(obj.message)); 6 | } 7 | ); 8 | 9 | if (typeof(console) !== 'undefined') { 10 | var TR = Y.Test.Runner; 11 | TR.subscribe(TR.COMPLETE_EVENT, function(obj) { 12 | console.log(JSON.stringify( 13 | { results: Y.Test.Format.JUnitXML(obj.results) })); 14 | } 15 | ); 16 | } 17 | }, '1.0', { requires: [ 'console' ] }); 18 | -------------------------------------------------------------------------------- /ch5/pg115: -------------------------------------------------------------------------------- 1 | /** 2 | * Return current stock price for given symbol 3 | * in the callback 4 | * 5 | * @method getPrice 6 | * @param symbol the ticker symbol 7 | * @param cb callback with results cb(error, value) 8 | * @param httpObj Optional HTTP object for injection 9 | * @return nothing 10 | **/ 11 | function getPrice(symbol, cb, httpObj) { 12 | var http = httpObj || require('http') 13 | , options = { 14 | host: 'download.finance.yahoo.com' // Thanks Yahoo! 15 | , path: '/d/quotes.csv?s=' + symbol + '&f=l1' 16 | } 17 | ; 18 | 19 | http.get(options, function(res) { 20 | res.on('data', function(d) { 21 | cb(null, d); 22 | }); 23 | }).on('error', function(e) { 24 | cb(e.message); 25 | }); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ch5/pg116: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple stub for HTTP object 3 | */ 4 | var events = require('events').EventEmitter 5 | , util = require('util') 6 | , myhttp = function() { // Dummy up NodeJS's 'http' object 7 | var _this = this ; 8 | events.call(this); 9 | this.get = function(options, cb) { 10 | cb(_this); 11 | return _this; 12 | }; 13 | } 14 | ; 15 | util.inherits(myhttp, events); 16 | -------------------------------------------------------------------------------- /ch5/pg117: -------------------------------------------------------------------------------- 1 | testPrice: function() { 2 | var symbol = 'YHOO' 3 | , stockPrice = 50 // Wishful thinking?? 4 | , _this = this 5 | , http = new myhttp() 6 | ; 7 | 8 | getPrice(symbol, function(err, price) { 9 | _this.resume(function() { 10 | Y.Assert.areEqual(stockPrice, price, "Prices not equal!"); 11 | }, http); // Inject our 'http' object 12 | http.fire('data', stockPrice); // Our mock data 13 | this.wait(1000); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ch5/pg117.1: -------------------------------------------------------------------------------- 1 | testPriceError: function() { 2 | var symbol = 'YHOO' 3 | , _this = this 4 | , http = new myhttp() 5 | ; 6 | 7 | getPrice(symbol, function(err, price) { 8 | _this.resume(function() { 9 | Y.Assert.areEqual(err, 'an error', "Did not get error!"); }, http); 10 | http.fire('error', { message: 'an error'} ); 11 | this.wait(1000); 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /ch5/pg121: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{QUERY_STRING} coverage=1 3 | RewriteRule ^(.*)$ make_coverage.pl?file=%{DOCUMENT_ROOT}/$1 [L] 4 | -------------------------------------------------------------------------------- /ch5/pg122: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use CGI; 3 | my $q = CGI->new; 4 | my $file = $q->param('file'); 5 | system("java -jar /path/to/yuitest_coverage.jar -o /tmp/$$.js $file"); 6 | print $q->header('application/JavaScript'); 7 | open(C, "/tmp/$$.js"); 8 | print ; 9 | -------------------------------------------------------------------------------- /ch5/pg123: -------------------------------------------------------------------------------- 1 | var Module = require('module') 2 | , path = require('path') 3 | , originalLoader = Module._load 4 | , coverageBase = '/tmp' 5 | , COVERAGE_ME = [] 6 | ; 7 | 8 | Module._load = coverageLoader; 9 | 10 | // Figure out what files to generate code coverage for 11 | // & put into COVERAGE_ME 12 | // And run those JS files thru yuitest-coverage.jar 13 | // and dump the output into coverageBase 14 | // Then execute tests 15 | // All calls to 'require' will filter thru this: 16 | function coverageLoader(request, parent, isMain) { 17 | if (COVERAGE_ME[request]) { 18 | request = PATH.join(coverageBase, path.basename(request)); 19 | } 20 | return originalLoader(request, parent, isMain); 21 | } 22 | // At the end dump the global _yuitest_coverage variable 23 | -------------------------------------------------------------------------------- /ch5/pg124: -------------------------------------------------------------------------------- 1 | var tempFile = PATH.join(coverageBase, PATH.basename(file)); 2 | , realFile = require.resolve(file) 3 | ; 4 | exec('java -jar ' + coverageJar + " -o " + tempFile + " " + realFile 5 | , function(err) { 6 | COVERAGE_ME[file] = 1; 7 | }); 8 | -------------------------------------------------------------------------------- /ch5/pg125: -------------------------------------------------------------------------------- 1 | var realFile = require.resolve(file) 2 | , coverFile = realFile.replace('.js', '.cover'); 3 | ; 4 | exec('java -jar ' + coverageJar + " -o " + coverFile + " " + realFile, function(err) {}); 5 | -------------------------------------------------------------------------------- /ch5/pg126: -------------------------------------------------------------------------------- 1 | var TestRunner = Y.Test.Runner; 2 | TestRunner.subscribe(TestRunner. TEST_SUITE_COMPLETE_EVENT, getResults); 3 | TestRunner.run(); 4 | function getResults(data) { 5 | var reporter = new Y.Test.Reporter( 6 | "http://www.yourserver.com/path/to/target" 7 | , Y.Test.Format.JUnitXML); 8 | reporter.report(results); 9 | } 10 | -------------------------------------------------------------------------------- /ch5/pg127: -------------------------------------------------------------------------------- 1 | function getResults(data) { 2 | // Use JUnitXML format for unit test results 3 | var reporter = new Y.Test.Reporter( 4 | "http://www.yourserver.com/path/to/target" 5 | , Y.Test.Format.JUnitXML); 6 | 7 | // Toss in coverage results 8 | reporter.addField("coverageResults", Y.Test.Runner.getCoverage(Y.Coverage.Format.JSON)); 9 | // Ship it 10 | reporter.report(results); 11 | } 12 | -------------------------------------------------------------------------------- /ch5/pg130: -------------------------------------------------------------------------------- 1 | my $old = '/a/b/c'; 2 | my $new = '/d/e/f'; 3 | open(F, "wrong.lcov"); 4 | open(G, ">right.lcov"); 5 | while() { 6 | s#^$old#$new#; 7 | print G; 8 | } 9 | close(G); 10 | close(F); 11 | -------------------------------------------------------------------------------- /ch5/pg131: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Running dummy unit test for APP_FILE

5 | 6 | 7 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ch5/pg132: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl 2 | use Getopt::Long; 3 | use File::Find; 4 | use File::Basename; 5 | 6 | my($debug, $test_dir, @src_dir, $src_base); 7 | my $src_key = 'src'; // root of source tree 8 | 9 | GetOptions ( 10 | "test_dir=s" => \$test_dir, 11 | 132 | Chapter 5: Code Coverage 12 | "src_dir=s" => \@src_dir, 13 | "src_base=s" => \$src_base, 14 | ) || die "Bad Options!\n";; 15 | 16 | my $src_files = {}; 17 | find(\&all_src_files, @src_dir); 18 | find(\&all_tested_files, $test_dir); 19 | 20 | sub all_src_files { 21 | return unless (/\.js$/); 22 | foreach my $src_dir (@src_dir) { 23 | $File::Find::name =~ s/^\Q$src_base\E//; 24 | } 25 | $src_files->{$File::Find::name}++; 26 | } 27 | 28 | sub all_tested_files { 29 | return unless (/\.html?$/); 30 | open(F, $_) || die "Can't open $_: $!\n"; 31 | while(my $line = ) { 32 | if ($line =~ /["']([^"]+?\/($src_key\/[^"]+?\.js))["']/) { 33 | my($full_file_path) = $2; 34 | print "Test file $File::Find::name is coveraging 35 | $full_file_path\n" if ($debug); 36 | delete $src_files->{$full_file_path}; 37 | }. 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ch6/pg140: -------------------------------------------------------------------------------- 1 | var webdriverjs = require("webdriverjs") 2 | , browser = webdriverjs.remote({ 3 | host: 'localhost' 4 | , port: 4444 5 | , desiredCapabilities: { browserName: 'firefox' } 6 | }) 7 | ; 8 | 9 | browser 10 | .testMode() 11 | .init() 12 | .url("http://search.yahoo.com") 13 | .setValue("#yschsp", "JavaScript") 14 | .submitForm("#sf") 15 | .tests.visible('#resultCount', true, 'Got result count') 16 | .end(); 17 | -------------------------------------------------------------------------------- /ch6/pg140.1: -------------------------------------------------------------------------------- 1 | var webdriverjs = require("webdriverjs") 2 | , browser = webdriverjs.remote({ 3 | host: 'localhost' 4 | , port: 4444 5 | , desiredCapabilities: { browserName: 'firefox' } 6 | }) 7 | ; 8 | 9 | browser 10 | .testMode() 11 | .init() 12 | .url("http://search.yahoo.com") 13 | .setValue("#yschsp", "javascript") 14 | .submitForm("#sf") 15 | .tests.visible('#resultCount', true, 'Got result count') 16 | .saveScreenshot('results.png') 17 | .end(); 18 | -------------------------------------------------------------------------------- /ch6/pg142: -------------------------------------------------------------------------------- 1 | var soda = require('soda') 2 | , browser = soda.createClient({ 3 | url: 'http://search.yahoo.com' 4 | , host: 'localhost' 5 | , browser: 'safari' 6 | }) 7 | ; 8 | 9 | browser 10 | .chain 11 | .session() 12 | .open('/') 13 | .type('yschsp', 'JavaScript') 14 | .submit('sf') 15 | .waitForPageToLoad(5000) 16 | .assertElementPresent('resultCount') 17 | .end(function(err) { 18 | browser.testComplete(function() { 19 | if (err) { 20 | console.log('Test failures: ' + err); 21 | } else { 22 | console.log('success!'); 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /ch6/pg144: -------------------------------------------------------------------------------- 1 | var casper = require('casper').create(); 2 | casper.start('http://search.yahoo.com/', function() { 3 | this.fill('form#sf', { "p": 'JavaScript' }, false); 4 | this.click('#yschbt'); 5 | }); 6 | casper.then(function() { 7 | this.test.assertExists('#resultCount', 'Got result count'); 8 | }); 9 | casper.run(function() { 10 | this.exit(); 11 | }); 12 | -------------------------------------------------------------------------------- /ch6/pg145: -------------------------------------------------------------------------------- 1 | var casper = require('casper').create(); 2 | casper.start('http://search.yahoo.com/', function() { 3 | this.fill('form#sf', { "p": 'JavaScript' }, false); 4 | this.click('#yschbt'); 5 | }); 6 | 7 | casper.then(function() { 8 | this.capture('results.png', { 9 | top: 0, 10 | left: 0, 11 | width: 1024, 12 | height: 768 13 | }); 14 | 15 | this.test.assertExists('#resultCount', 'Got result count'); 16 | }); 17 | 18 | casper.run(function() { 19 | this.exit(); 20 | }); 21 | -------------------------------------------------------------------------------- /ch6/pg146: -------------------------------------------------------------------------------- 1 | var casper = require('casper').create(); 2 | casper.start('http://search.yahoo.com/', function() { 3 | this.fill('form#sf', { "p": 'JavaScript' }, false); 4 | this.click('#yschbt'); 5 | }); 6 | casper.then(function() { 7 | this.capture('results.png', { 8 | top: 0, 9 | left: 0, 10 | width: 1024, 11 | height: 768 12 | }); 13 | 14 | this.test.assertExists('#resultCount', 'Got result count'); 15 | }); 16 | 17 | casper.run(function() { 18 | this.test.renderResults(true, 0, 'test-results.xml'); 19 | }); 20 | -------------------------------------------------------------------------------- /ch6/pg149: -------------------------------------------------------------------------------- 1 | var Proxy = require('browsermob-proxy').Proxy 2 | , fs = require('fs') 3 | , proxy = new Proxy() 4 | ; 5 | 6 | proxy.doHAR('http://yahoo.com', function(err, data) { 7 | if (err) { 8 | console.error('ERROR: ' + err); 9 | } else { 10 | fs.writeFileSync('yahoo.com.har', data, 'utf8'); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /ch6/pg149.1: -------------------------------------------------------------------------------- 1 | var Proxy = require('browsermob-proxy').Proxy 2 | , webdriverjs = require("webdriverjs") 3 | , fs = require('fs') 4 | , proxy = new Proxy() 5 | ; 6 | 7 | /* 8 | * Call into the proxy with a 'name' for this session, a Selenium 9 | * function to run the interaction we want to capture, and 10 | * finally a callback that will contain either the HAR data or 11 | * an error 12 | */ 13 | proxy.cbHAR('search.yahoo.com', doSeleniumStuff, function(err, data) { 14 | if (err) { 15 | console.error('Error capturing HAR: ' + err); 16 | } else { 17 | fs.writeFileSync('search.yahoo.com.har', data, 'utf8'); 18 | } 19 | }); 20 | 21 | /* 22 | * This is the Selenium function that gets passed the proxy webdriverjs 23 | * should use and a callback to call when the interaction is done 24 | */ 25 | function doSeleniumStuff(proxy, cb) { 26 | var browser = webdriverjs.remote({ 27 | host: 'localhost' 28 | , port: 4444 29 | , desiredCapabilities: { 30 | browserName: 'firefox' 31 | , seleniumProtocol: 'WebDriver' 32 | , proxy: { httpProxy: proxy } 33 | } 34 | }); 35 | 36 | // Just run our regular Selenium stuff - note this can just 37 | // be your regular test code or something special you want 38 | // to capture with a HAR 39 | // Just pass the browsermob-proxy callback to 'end()' 40 | browser 41 | .testMode() 42 | .init() 43 | .url("http://search.yahoo.com") 44 | .setValue("#yschsp", "JavaScript") 45 | .submitForm("#sf") 46 | .tests.visible('#resultCount', true, 'Got result count') 47 | .saveScreenshot('results.png') 48 | .end(cb); 49 | } 50 | -------------------------------------------------------------------------------- /ch6/pg151: -------------------------------------------------------------------------------- 1 | var Proxy = require('browsermob-proxy').Proxy 2 | , spawn = require('child_process').spawn 3 | , fs = require('fs') 4 | ; 5 | 6 | var proxy = new Proxy(); 7 | proxy.cbHAR('MyCoolHARFile', doCasperJSStuff, function(err, data) { 8 | if (err) { 9 | console.error('ERR: ' + err); 10 | } else { 11 | fs.writeFileSync('casper.har', data, 'utf8'); 12 | } 13 | }); 14 | 15 | function doCasperJSStuff(proxy, cb) { 16 | casperjs = spawn('bin/casperjs' , [ '--proxy=' + proxy, process.argv[2] ]); 17 | casperjs.on('exit', cb); 18 | } 19 | -------------------------------------------------------------------------------- /ch6/pg161: -------------------------------------------------------------------------------- 1 | var myLoop = new loop.Loop( 2 | function(finished, args) { 3 | getMemory(); 4 | getCPU(); 5 | finished(); 6 | } 7 | , [] // No args 8 | , [] // No conditions 9 | , .2 // Once every 5 seconds 10 | ); 11 | 12 | myLoop.start(); 13 | -------------------------------------------------------------------------------- /ch6/pg161.1: -------------------------------------------------------------------------------- 1 | var reporting = require('nodeload/lib/reporting') 2 | , report = reporting.REPORT_MANAGER.getReport('System Usage') 3 | , memChart = report.getChart('Memory Usage') 4 | , cpuChart = report.getChart('CPU Usage') 5 | , loop = require('nodeload/lib/loop') 6 | , fs = require('fs') 7 | ; 8 | -------------------------------------------------------------------------------- /ch6/pg161.2: -------------------------------------------------------------------------------- 1 | function getMemory() { 2 | var memData = getProc('meminfo', /\s*kb/i); 3 | memChart.put({ 'Free Memory': memData['MemFree'] , 'Free Swap': memData['SwapFree'] }); 4 | report.summary['Total Memory'] = memData['MemTotal']; 5 | report.summary['Total Swap'] = memData['SwapTotal']; 6 | } 7 | -------------------------------------------------------------------------------- /ch6/pg162: -------------------------------------------------------------------------------- 1 | function getCPU() { 2 | var meminfo = fs.readFileSync('/proc/loadavg', 'utf8') 3 | , vals = meminfo.split(/\s+/) 4 | , cpuInfo = getProc('cpuinfo') 5 | ; 6 | 7 | cpuChart.put( { 8 | '1 Min': vals[0] 9 | , '5 Min': vals[1] 10 | , '15 Min': vals[2] 11 | }); 12 | report.summary['CPU'] = cpuInfo['model name']; 13 | } 14 | -------------------------------------------------------------------------------- /ch6/pg163: -------------------------------------------------------------------------------- 1 | function createClosure() { 2 | var bigHairyVariable = { ... }, wow = 'wow'; 3 | return { 4 | a: bigHairyVariable 5 | , b: wow 6 | , c: 87 7 | , d: function() { return bigHairyVariable; } 8 | }; 9 | } 10 | // never use global.a - wasted a lot of memory - a leak? 11 | var global = createClosure(); 12 | -------------------------------------------------------------------------------- /ch7/pg179: -------------------------------------------------------------------------------- 1 | function getIterator(countBy, startAt, upTill) { 2 | countBy = countBy || 1; 3 | startAt = startAt || 0; 4 | upTill = upTill || 100; 5 | var current = startAt; 6 | return function() { 7 | current += countBy; 8 | return (current > upTill) ? NaN : current; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /ch7/pg180: -------------------------------------------------------------------------------- 1 | function getIterator(countBy, startAt, upTill) { 2 | countBy = countBy || 1; 3 | startAt = startAt || 0; 4 | upTill = upTill || 100; 5 | var current = startAt 6 | , ret = function() { 7 | current += countBy; 8 | return (current > upTill) ? NaN : current; 9 | } 10 | ; 11 | 12 | ret.displayName = "Iterator from " + startAt + " until " + upTill + " by " countBy; 13 | return ret; 14 | } 15 | -------------------------------------------------------------------------------- /ch7/pg188: -------------------------------------------------------------------------------- 1 | var pDebug = require('pDebug').pDebug 2 | , debug = new pDebug({ eventHandler: function(event) { 3 | console.log('Event'); console.log(event); } } 4 | ) 5 | ; 6 | 7 | debug.connect(function() { 8 | var msg = { command: 'continue' }; 9 | debug.send(msg, function(req, resp) { 10 | console.log('REQ: '); 11 | console.log(req); 12 | console.log('RES: '); 13 | console.log(resp); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /ch7/pg190: -------------------------------------------------------------------------------- 1 | var webdriverjs = require("webdriverjs") 2 | , browser = webdriverjs.remote({ 3 | host: 'localhost' 4 | , port: 4444 5 | , desiredCapabilities: { 6 | browserName: 'chrome' 7 | , seleniumProtocol: 'WebDriver' 8 | , 'chrome.switches': [ 9 | '--remote-debugging-port=9222' 10 | , '--user-data-dir=remote-profile' 11 | ] 12 | } 13 | }) 14 | ; 15 | 16 | browser.addCommand("startCapture", startCapture); 17 | browser 18 | .init() 19 | .url('http://search.yahoo.com') 20 | .startCapture() 21 | .setValue("#yschsp", "JavaScript") 22 | .submitForm("#sf") 23 | .saveScreenshot('results.png') 24 | .end(); 25 | -------------------------------------------------------------------------------- /ch7/pg191: -------------------------------------------------------------------------------- 1 | function startCapture(ready) { 2 | var http = require('http') 3 | , options = { 4 | host: 'localhost' 5 | , port: 9222 6 | , path: '/json' 7 | } 8 | ; 9 | 10 | http.get(options, function(res) { 11 | res.on('data', function (chunk) { 12 | var resObj = JSON.parse(chunk); 13 | connectToDebugger(resObj[0], ready); 14 | }); 15 | }).on('error', function(e) { 16 | console.log("Got error: " + e.message); 17 | } 18 | -------------------------------------------------------------------------------- /ch7/pg192: -------------------------------------------------------------------------------- 1 | function connectToDebugger(obj, ready) { 2 | var fs = require('fs') 3 | , WebSocket = require('faye-websocket') 4 | , ws = new WebSocket.Client(obj.webSocketDebuggerUrl) 5 | , msg = { 6 | id: 777 7 | , method: "Timeline.start" 8 | , params: { 9 | maxCallStackDepth: 10 10 | } 11 | } 12 | , messages = '' 13 | ; 14 | 15 | ws.onopen = function(event) { 16 | ws.send(JSON.stringify(msg)); 17 | ready(); 18 | }; 19 | 20 | ws.onmessage = function(event) { 21 | var obj = JSON.parse(event.data); 22 | if (obj.method && obj.method === 'Timeline.eventRecorded') { 23 | obj.record = obj.params.record; // Zany little hack 24 | messages += JSON.stringify(obj) + '\n'; 25 | } 26 | }; 27 | 28 | ws.onclose = function(event) { 29 | var header = '\n' 30 | + '\n
' 31 | , footer = '
' 32 | ; 33 | 34 | ws = null; 35 | fs.writeFileSync('DUMP.speedtracer.html', header + messages + footer, 'utf8'); 36 | }; 37 | -------------------------------------------------------------------------------- /ch7/pg196: -------------------------------------------------------------------------------- 1 | // Open page from the command line in PhantomJS 2 | var page = new WebPage(); 3 | page.onError = function (msg, trace) { 4 | console.log(msg); 5 | trace.forEach(function(item) { 6 | console.log(' ', item.file, ':', item.line); 7 | }) 8 | } 9 | 10 | page.open(phantom.args[0], function (status) { 11 | // Check for page load success 12 | if (status !== "success") { 13 | console.log("Unable to access network"); 14 | } else { 15 | setInterval(function() {}, 200000); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /ch8/Makefile: -------------------------------------------------------------------------------- 1 | DO_COVERAGE=1 2 | RELEASE=release 3 | UGLIFY=uglifyjs 4 | 5 | SRC := $(shell find src -name '*.js') 6 | OBJS := $(patsubst %.js,%.jc,$(SRC)) 7 | 8 | %.jc : %.js 9 | -mkdir -p $(RELEASE)/$(@D) 10 | $(UGLIFY) -o $(RELEASE)/$(*D)/$(*F).js $< 11 | 12 | CHECKSTYLE=checkstyle 13 | STYLE := $(patsubst %.js,%.xml,$(SRC)) 14 | %.xml : %.js 15 | -mkdir -p $(CHECKSTYLE)/$(@D) 16 | -jscheckstyle --checkstyle $< > $(CHECKSTYLE)/$(*D)/$(*F).xml 17 | 18 | JSLINT=jslint 19 | JSL := $(patsubst %.js,%.jslint,$(SRC)) 20 | %.jslint : %.js 21 | -mkdir -p $(JSLINT)/$(@D) 22 | ./hudson_jslint.pl $< > $(JSLINT)/$(*D)/$(*F).jslint 23 | 24 | prod: unit_tests $(OBJS) $(STYLE) $(JSL) 25 | 26 | jslint: $(JSL) 27 | 28 | pre: 29 | ifdef WORKSPACE 30 | npm config set jute:docRoot '$(WORKSPACE)' 31 | npm restart jute 32 | npm config list jute 33 | endif 34 | 35 | server_side_unit_tests: pre 36 | cd test && find server_side -name '*.js' -exec echo "{}?do_coverage=$(DO_COVERAGE)" \; | jute_submit_test --v8 --test - 37 | 38 | client_side_unit_tests: pre 39 | cd test && find client_side -name '*.html' -exec echo "{}?do_coverage=$(DO_COVERAGE)" \; | jute_submit_test --v8 --test - 40 | 41 | unit_tests: server_side_unit_tests client_side_unit_tests make_total_lcov 42 | 43 | OUTPUT_DIR=output 44 | TOTAL_LCOV_FILE=$(OUTPUT_DIR)/lcov.info 45 | make_total_lcov: 46 | @echo "OUTPUT DIR: ${OUTPUT_DIR}" 47 | @echo "TOTAL LCOV FILE: ${TOTAL_LCOV_FILE}" 48 | /bin/rm -f /tmp/lcov.info ${TOTAL_LCOV_FILE} 49 | find $(OUTPUT_DIR) -name lcov.info -exec echo '-a {}' \; | xargs lcov > /tmp/lcov.info 50 | cp /tmp/lcov.info ${TOTAL_LCOV_FILE} 51 | /bin/rm -rf $(OUTPUT_DIR)/lcov-report 52 | genhtml -o $(OUTPUT_DIR)/lcov-report $(TOTAL_LCOV_FILE) 53 | python ./conv.py $(TOTAL_LCOV_FILE) -b . -o output/cob.xml 54 | 55 | .PHONY: server_side_unit_tests client_side_unit_tests unit_tests pre make_total_lcov 56 | -------------------------------------------------------------------------------- /ch8/hudson_jslint.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use JSON; 4 | use Cwd qw(abs_path); 5 | use XML::Writer; 6 | 7 | my $f = abs_path(shift); 8 | 9 | my @back = `jslint --json $f`; 10 | my $res = decode_json(join '', @back); 11 | my @errors; 12 | foreach my $error (@{$res->[1]}) { 13 | if ($error->{id}) { 14 | push @errors, $error; 15 | } 16 | } 17 | 18 | my $xml = new XML::Writer(); 19 | $xml->startTag('jslint'); 20 | $xml->startTag('file', name => $res->[0]); 21 | foreach my $error (@errors) 22 | { 23 | $xml->emptyTag('issue', 24 | line => $error->{line}, 25 | reason => $error->{reason}, 26 | evidence => $error->{evidence} 27 | ); 28 | } 29 | $xml->endTag('file'); 30 | $xml->endTag('jslint'); 31 | $xml->end; 32 | 33 | exit(0); 34 | 35 | -------------------------------------------------------------------------------- /ch8/pg224: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Passed from SVN 4 | my $REPO = shift; 5 | my $TXN = shift; 6 | 7 | # Set up PATH 8 | $ENV{PATH} = "$ENV{PATH}:/usr/bin:/usr/local/bin"; 9 | 10 | # The log message 11 | my @logmsg = `svnlook log -t "$TXN" "$REPO"`; 12 | #print STDERR @logmsg; 13 | 14 | # Get file changeset 15 | my @changes = `svnlook changed --transaction "$TXN" "$REPO"`; 16 | my $failed = 0; 17 | #print STDERR @changes; 18 | 19 | # Find JS files 20 | foreach (@changes) { 21 | my($cmd, $file) = split(/\s+/); 22 | # Only JSLint *.js files that haven't been deleted! 23 | if ($file =~ /\.js$/ && $cmd ne 'D') { 24 | # Text of changed file: 25 | # my @cat = `svnlook cat "$REPO" --transaction "$TXN" $file`; 26 | # OR just grab the pre-committed file itself directly 27 | # This script runs from the directory of the commit itself so 28 | # these relative paths are the uncommitted versions 29 | my @jslint = `/usr/local/bin/jslint $file`; 30 | if ($?) { 31 | print STDERR '-' x 20, "\n"; 32 | print STDERR "JSLint errors in $file:\n"; 33 | print STDERR '-' x 20; 34 | print STDERR @jslint; 35 | $failed++; 36 | } 37 | } 38 | } 39 | 40 | # STDERR goes back to client if failed 41 | exit $failed; 42 | -------------------------------------------------------------------------------- /ch8/pg225: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Get file list 4 | my @files = `git diff --cached --name-only HEAD`; 5 | my $failed = 0; 6 | foreach (@files) { 7 | # Check *.js files only 8 | if (/\.js$/) { 9 | my @style = `jscheckstyle --violations $_`; 10 | if ($?) { 11 | # Send pretty error output to client 12 | print @style; 13 | $failed++; 14 | } 15 | } 16 | } 17 | exit $failed; 18 | --------------------------------------------------------------------------------