├── .gitignore ├── 00_hello ├── HelloSpec.js └── index.html ├── 01_properties ├── index.html └── propertiesSpec.js ├── 02_loops ├── LoopsSpec.js └── index.html ├── 03_calculator ├── calculatorSpec.js └── index.html ├── 04_temperature ├── index.html ├── matchers.js └── temperatureSpec.js ├── 05_dom ├── colorSpec.js ├── domSpec.js ├── domTemperatureWidgetSpec.js ├── index.html ├── numberSpec.js └── temperatureDemo.html ├── 06_bouncing_ball ├── bouncingBallSpec.js └── index.html ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── abstract.txt ├── assets └── style.css ├── index.html ├── jasmine.yml └── todo.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /00_hello/HelloSpec.js: -------------------------------------------------------------------------------- 1 | describe("Hello", function() { 2 | it("says hello", function() { 3 | expect(hello()).toEqual("Hello!"); 4 | }); 5 | 6 | it("says hello to someone", function() { 7 | expect(hello("Fred")).toEqual("Hello, Fred!"); 8 | }); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /00_hello/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test-First Teaching: learn_javascript: hello 4 | 5 | 6 | 7 |
8 |

TestFirst.org

9 |

the home of test-first teaching

10 |
11 | 24 |

hello

25 |

Say Hello to JavaScript

26 | 27 |

Open a console

28 | 29 |

Once the console is open, change directories into the 00_hello directory

30 | 31 |
cd 00_hello
32 | 
33 | 34 |

Run the Jasmine server

35 | 36 |
rake jasmine
37 | 
38 | 39 |

Open the spec page in your web browser

40 | 41 |
open http://localhost:8888/  # Mac only
42 | 
43 | 44 |

You should see a failing spec. This is good! Now it's your turn to make it pass.

45 | 46 |

Open the spec file in a text editor

47 | 48 |
open HelloSpec.js  # Mac only
49 | 
50 | 51 |

This file is written in a particular syntax for a testing tool named Jasmine BDD. Here are a few important rules:

52 | 53 |
    54 |
  1. each "describe" starts a related group of specs
  2. 55 |
  3. each "it" starts a single spec
  4. 56 |
  5. each "expect" describes a single assertion about your source code
  6. 57 |
58 | 59 | 60 |

For example, expect(hello()).toEqual("Hello!") means "when I call the function hello(), I expect its result to be equal to the string "Hello!""

61 | 62 |

Create the source file

63 | 64 |

Create and open a file named hello.js. Inside it, put the following text:

65 | 66 |
function hello() {
67 | }
68 | 
69 | 70 |

Reload the spec page in the browser

71 | 72 |

You should still see the same failing spec, but it should be failing for a different reason.

73 | 74 |

Make the first spec pass

75 | 76 |

Make hello.js look like this:

77 | 78 |
function hello() {
79 |     return "Hello!"
80 | }
81 | 
82 | 83 |

Reload the spec page in the browser. The first spec should pass!

84 | 85 |

Now do a little victory dance!

86 | 87 |

Also, high five your partner. If you don't have a partner, high five your dog.

88 | 89 |

Next steps

90 | 91 |

Make the next spec pass. This will require an if statement and some messing around with the function declaration.

92 | 93 |

Here's a hint: In JavaScript, if the caller forgets to pass in an argument to a function, then its value is undefined (which is almost the same thing as false).

94 |
95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /01_properties/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test-First Teaching: learn_javascript: properties 4 | 5 | 6 | 7 |
8 |

TestFirst.org

9 |

the home of test-first teaching

10 |
11 | 24 |

properties

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /01_properties/propertiesSpec.js: -------------------------------------------------------------------------------- 1 | describe("Properties", function() { 2 | var object; 3 | 4 | beforeEach(function() { 5 | object = {}; 6 | setSomePropertiesOn(object); 7 | }); 8 | 9 | describe("setSomePropertiesOn", function() { 10 | it("sets x to 7", function() { 11 | expect(object.x).toEqual(7); 12 | }); 13 | 14 | it("sets y to 8 (and we can use a string to access it)", function() { 15 | expect(object['y']).toEqual(8); 16 | }); 17 | 18 | it("sets the property 'onePlus' to a function that adds one to its parameter", function() { 19 | expect(object.onePlus(4)).toEqual(5); 20 | expect(object['onePlus'](123)).toEqual(124); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /02_loops/LoopsSpec.js: -------------------------------------------------------------------------------- 1 | describe("Loops", function() { 2 | describe("repeat", function() { 3 | it("is empty with 0 repeats", function() { 4 | expect(repeat("yo", 0)).toEqual(""); 5 | }); 6 | it("repeats its argument once", function() { 7 | expect(repeat("yo", 1)).toEqual("yo"); 8 | }); 9 | it("repeats its argument twice", function() { 10 | expect(repeat("yo", 2)).toEqual("yoyo"); 11 | }); 12 | it("repeats its argument many times", function() { 13 | expect(repeat("yo", 10)).toEqual("yoyoyoyoyoyoyoyoyoyo"); 14 | }); 15 | }); 16 | 17 | describe("looping over arrays", function() { 18 | 19 | // try to implement this join() function without using the built-in String#join function 20 | describe("join", function() { 21 | it("turns an empty array into an empty string", function() { 22 | expect(join([])).toEqual(""); 23 | }); 24 | 25 | it("turns an array with one element into a string", function() { 26 | expect(join(['a'])).toEqual("a"); 27 | }); 28 | 29 | it("turns an array with many elements into a string", function() { 30 | expect(join(['apple', 'banana', 'cherry'])).toEqual("applebananacherry"); 31 | }); 32 | 33 | it("inserts a delimiter between elements", function() { 34 | expect(join(['apple', 'banana', 'cherry'], '/')).toEqual("apple/banana/cherry"); 35 | }); 36 | 37 | // This test is to make sure you don't use "for (var i in a)" on an array 38 | it("ignores non-indexed properties set on the array object", function() { 39 | var array = ['apple', 'banana', 'cherry']; 40 | 41 | array['type'] = 'fruits'; 42 | expect(array.type).toEqual('fruits'); 43 | 44 | array.first = function() { return this[0]; } 45 | expect(array.first()).toEqual('apple'); 46 | 47 | expect(join(array)).toEqual("applebananacherry"); 48 | }); 49 | 50 | }); 51 | 52 | describe("sum", function() { 53 | it("computes the sum of an empty array", function() { 54 | expect(sum([])).toEqual(0); 55 | }); 56 | 57 | it("computes the sum of an array of one number", function() { 58 | expect(sum([7])).toEqual(7); 59 | }); 60 | 61 | it("computes the sum of an array of two numbers", function() { 62 | expect(sum([7,11])).toEqual(18); 63 | }); 64 | 65 | it("computes the sum of an array of many numbers", function() { 66 | expect(sum([1,3,5,7,9])).toEqual(25); 67 | }); 68 | }); 69 | }); 70 | 71 | // hint: to loop over elements of a hash, you can use 72 | // for (var key in hash) {} 73 | describe("looping over hashes", function() { 74 | describe("paramify", function() { 75 | it("works on an empty hash", function() { 76 | expect(paramify({})).toEqual(""); 77 | }); 78 | 79 | it("converts a hash with one element", function() { 80 | expect(paramify({size: 14})).toEqual("size=14"); 81 | }); 82 | 83 | it("converts a hash with two elements", function() { 84 | expect(paramify({height: 74, width: 12})).toEqual("height=74&width=12"); 85 | }); 86 | 87 | it("converts a hash with many elements", function() { 88 | var hash = {a:1,b:2,c:3,d:4,e:5,f:6} 89 | expect(paramify(hash)).toEqual("a=1&b=2&c=3&d=4&e=5&f=6"); 90 | }); 91 | 92 | // this one might be a bit tricky ;-) 93 | it("outputs the parameters in alphabetical order", function() { 94 | var hash = {f:6,e:5,d:4,c:3,b:2,a:1}; 95 | expect(paramify(hash)).toEqual("a=1&b=2&c=3&d=4&e=5&f=6"); 96 | }); 97 | 98 | // Advanced. Change "xit" to "it" to run this test. 99 | xit("skips properties of the object's prototype", function() { 100 | var Alphabet = function() { 101 | this.a = 1; 102 | this.b = 2; 103 | } 104 | Alphabet.prototype = {c: 3}; 105 | expect(paramify(new Alphabet())).toEqual("a=1&b=2"); 106 | }); 107 | }); 108 | }); 109 | 110 | // Test-Driving Bonus: once the above tests pass, 111 | // write tests and code for the following. 112 | // See http://en.wikipedia.org/wiki/Factorial 113 | // You can either use iteration (loops) or recursion. 114 | // (Recursion is easier, but might blow your mind.) 115 | describe("factorial", function() { 116 | it("computes the factorial of 0"); 117 | it("computes the factorial of 1"); 118 | it("computes the factorial of 2"); 119 | it("computes the factorial of 5"); 120 | it("computes the factorial of 10"); 121 | }); 122 | 123 | }); 124 | -------------------------------------------------------------------------------- /02_loops/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test-First Teaching: learn_javascript: loops 4 | 5 | 6 | 7 |
8 |

TestFirst.org

9 |

the home of test-first teaching

10 |
11 | 24 |

loops

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /03_calculator/calculatorSpec.js: -------------------------------------------------------------------------------- 1 | describe("Calculator", function() { 2 | 3 | var calculator; 4 | 5 | beforeEach(function() { 6 | calculator = new Calculator(); 7 | }); 8 | 9 | it("initially has 0", function() { 10 | expect(calculator.value()).toEqual(0); 11 | }); 12 | 13 | it("can add a number", function() { 14 | calculator.add(2); 15 | expect(calculator.value()).toEqual(2); 16 | }); 17 | 18 | it("can add two numbers", function() { 19 | calculator.add(2); 20 | calculator.add(3); 21 | expect(calculator.value()).toEqual(5); 22 | }); 23 | 24 | it("can add many numbers", function() { 25 | calculator.add(2); 26 | calculator.add(3); 27 | calculator.add(4); 28 | expect(calculator.value()).toEqual(9); 29 | }); 30 | 31 | it("can subtract a number", function() { 32 | calculator.subtract(2); 33 | expect(calculator.value()).toEqual(-2); 34 | }); 35 | 36 | it("can add and subtract", function() { 37 | calculator.add(3); 38 | calculator.subtract(2); 39 | expect(calculator.value()).toEqual(1); 40 | }); 41 | 42 | // See http://en.wikipedia.org/wiki/Reverse_Polish_notation 43 | describe("using reverse polish notation", function() { 44 | it("adds two numbers", function() { 45 | calculator.push(2); 46 | calculator.push(3); 47 | calculator.plus(); 48 | expect(calculator.value()).toEqual(5); 49 | }); 50 | 51 | it("adds three numbers", function() { 52 | calculator.push(2); 53 | calculator.push(3); 54 | calculator.push(4); 55 | calculator.plus(); 56 | expect(calculator.value()).toEqual(7); 57 | calculator.plus(); 58 | expect(calculator.value()).toEqual(9); 59 | }); 60 | 61 | it("adds and subtracts", function() { 62 | calculator.push(2); 63 | calculator.push(3); 64 | calculator.push(4); 65 | calculator.minus(); 66 | expect(calculator.value()).toEqual(-1); 67 | calculator.plus(); 68 | expect(calculator.value()).toEqual(1); 69 | }); 70 | 71 | it("multiplies and divides", function() { 72 | calculator.push(2); 73 | calculator.push(3); 74 | calculator.push(4); 75 | calculator.divide(); 76 | expect(calculator.value()).toEqual(0.75); 77 | calculator.times(); 78 | expect(calculator.value()).toEqual(1.5); 79 | }); 80 | 81 | it("fails informatively when there's not enough values stashed away", function() { 82 | expect(function() { 83 | calculator.plus(); 84 | }).toThrow("calculator is empty"); 85 | 86 | expect(function() { 87 | calculator.minus(); 88 | }).toThrow("calculator is empty"); 89 | 90 | expect(function() { 91 | calculator.times(); 92 | }).toThrow("calculator is empty"); 93 | 94 | expect(function() { 95 | calculator.divide(); 96 | }).toThrow("calculator is empty"); 97 | }); 98 | 99 | }); 100 | 101 | }); 102 | -------------------------------------------------------------------------------- /03_calculator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test-First Teaching: learn_javascript: calculator 4 | 5 | 6 | 7 |
8 |

TestFirst.org

9 |

the home of test-first teaching

10 |
11 | 24 |

calculator

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /04_temperature/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test-First Teaching: learn_javascript: temperature 4 | 5 | 6 | 7 |
8 |

TestFirst.org

9 |

the home of test-first teaching

10 |
11 | 24 |

temperature

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /04_temperature/matchers.js: -------------------------------------------------------------------------------- 1 | beforeEach(function() { 2 | this.addMatchers({ 3 | toBeCloseTo: function(expected, precision) { 4 | precision = precision || 2; 5 | var multiplier = Math.pow(10, precision); 6 | var actual = Math.round(this.actual * multiplier); 7 | expected = Math.round(expected * multiplier); 8 | return expected == actual; 9 | } 10 | }) 11 | }); 12 | -------------------------------------------------------------------------------- /04_temperature/temperatureSpec.js: -------------------------------------------------------------------------------- 1 | // These Temperature exercises progress through three stages: 2 | // 1. Two functions, f2c and c2f, that demonstrate the simple equations for converting between fahrenheit and celcius degrees 3 | // 2. An object, Temperature, that encapsulates the temperature value 4 | // 3. Making sure that object uses *private data* (and privileged methods) instead of public properties to store the value 5 | 6 | describe("Temperature", function() { 7 | describe("f2c", function() { 8 | it("converts freezing temperature", function() { 9 | expect(f2c(32)).toEqual(0); 10 | }); 11 | 12 | it("converts boiling temperature", function() { 13 | expect(f2c(212)).toEqual(100); 14 | }); 15 | 16 | it("converts body temperature", function() { 17 | expect(f2c(98.6)).toEqual(37); 18 | }); 19 | 20 | it("converts an arbitrary temperature", function() { 21 | expect(f2c(68)).toEqual(20); 22 | }); 23 | }); 24 | 25 | describe("c2f", function() { 26 | it("converts freezing temperature", function() { 27 | expect(c2f(0)).toEqual(32); 28 | }); 29 | 30 | it("converts boiling temperature", function() { 31 | expect(c2f(100)).toEqual(212); 32 | }); 33 | 34 | it("converts body temperature", function() { 35 | expect(c2f(37)).toBeCloseTo(98.6); 36 | // Why do we need to use toBeCloseTo? 37 | // To avoid floating point precision errors. 38 | // See http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems 39 | // Try replacing "toBeCloseTo" with "toEqual" and see what happens. 40 | }); 41 | 42 | it("converts an arbitrary temperature", function() { 43 | expect(c2f(20)).toEqual(68); 44 | }); 45 | }); 46 | 47 | describe("the Temperature object", function() { 48 | var temperature; 49 | beforeEach(function() { 50 | temperature = new Temperature(); 51 | }); 52 | 53 | it("stores degrees Fahrenheit", function() { 54 | temperature.setFahrenheit(32); 55 | expect(temperature.fahrenheit()).toEqual(32); 56 | }); 57 | 58 | it("converts from Fahrenheit to Celcius", function() { 59 | temperature.setFahrenheit(32); 60 | expect(temperature.celcius()).toEqual(0); 61 | }); 62 | 63 | it("stores degrees Celcius", function() { 64 | temperature.setCelcius(0); 65 | expect(temperature.celcius()).toEqual(0); 66 | }); 67 | 68 | it("converts from Celcius to Fahrenheit", function() { 69 | temperature.setCelcius(0); 70 | expect(temperature.fahrenheit()).toEqual(32); 71 | }); 72 | 73 | it("can be constructed with degrees Fahrenheit", function() { 74 | temperature = new Temperature(32); 75 | expect(temperature.celcius()).toEqual(0); 76 | }); 77 | 78 | // Bonus refactoring: once the above specs pass, 79 | // refactor the original f2c and c2f functions 80 | // to use your new Temperature class. 81 | 82 | it("privately encapsulates its data", function() { 83 | var temperature; 84 | temperature = new Temperature(32); 85 | for (var property in temperature) { 86 | // This assures that there are no data values at all on the object, just methods 87 | expect(typeof(temperature[property])).toEqual('function'); 88 | } 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /05_dom/colorSpec.js: -------------------------------------------------------------------------------- 1 | describe("Color", function() { 2 | it("is constructed with red, green, and blue values", function() { 3 | var color = new Color(1,2,3); 4 | expect(color.red).toEqual(1); 5 | expect(color.green).toEqual(2); 6 | expect(color.blue).toEqual(3); 7 | }); 8 | 9 | it("can emit a hex value", function() { 10 | expect(new Color(0,0,0).toHex()).toEqual("#000000"); 11 | expect(new Color(255,255,255).toHex()).toEqual("#ffffff"); 12 | expect(new Color(60,70,80).toHex()).toEqual("#3c4650"); 13 | }); 14 | 15 | describe("gradient", function() { 16 | var black = new Color(0,0,0); 17 | var white = new Color(255,255,255); 18 | 19 | it("can calculate the midpoint of two colors", function() { 20 | var gray = black.gradient(white, 0.5); 21 | expect(gray.toHex()).toEqual("#7f7f7f"); 22 | }); 23 | 24 | it("can calculate an arbitrary point between two colors", function() { 25 | var gray = black.gradient(white, 0.1); 26 | expect(gray.toHex()).toEqual("#191919"); 27 | }); 28 | 29 | it("works if the colors are reversed", function() { 30 | var gray = white.gradient(black, 0.1); 31 | expect(gray.toHex()).toEqual("#e5e5e5"); 32 | }); 33 | 34 | it("hits the minimum color at 0 distance", function() { 35 | var min = black.gradient(white, 0); 36 | expect(min.toHex()).toEqual("#000000"); 37 | }); 38 | 39 | it("hits the maximum color at 1.0 distance", function() { 40 | var max = black.gradient(white, 1.0); 41 | expect(max.toHex()).toEqual("#ffffff"); 42 | }); 43 | 44 | it("hits the minimum color at negative distance", function() { 45 | var min = black.gradient(white, -0.5); 46 | expect(min.toHex()).toEqual("#000000"); 47 | }); 48 | 49 | it("hits the maximum color at greater than 1 distance", function() { 50 | var max = black.gradient(white, 1.5); 51 | expect(max.toHex()).toEqual("#ffffff"); 52 | }); 53 | 54 | }); 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /05_dom/domSpec.js: -------------------------------------------------------------------------------- 1 | var jasmineContent; 2 | 3 | describe("dom", function() { 4 | beforeEach(function() { 5 | // set some fixture HTML inside the Jasmine runner page 6 | jasmineContent = document.getElementById("jasmine_content"); 7 | jasmineContent.innerHTML = "pie"; 8 | }); 9 | it("reads HTML content from the test page", function() { 10 | expect(jasmineContent.innerHTML).toEqual("pie"); 11 | }); 12 | }); 13 | 14 | -------------------------------------------------------------------------------- /05_dom/domTemperatureWidgetSpec.js: -------------------------------------------------------------------------------- 1 | describe("TemperatureWidget", function() { 2 | var widget, jasmineContent; 3 | 4 | beforeEach(function() { 5 | jasmineContent = document.getElementById("jasmine_content"); 6 | // set up some sample HTML in the test page 7 | // (backslash is a line contination char) 8 | jasmineContent.innerHTML = " \ 9 |
\ 10 |

Fahrenheit:

\ 11 |

Celcius: ???

\ 12 |

\ 13 |
"; 14 | 15 | // create the TemperatureWidget for use in later tests 16 | widget = new TemperatureWidget(); 17 | }); 18 | 19 | it("can locate the widget element", function() { 20 | expect(widget.widgetElement.id).toEqual('temperatureWidget'); 21 | expect(widget.widgetElement instanceof HTMLElement).toBeTruthy(); 22 | }); 23 | 24 | it("can locate the fahrenheit element", function() { 25 | expect(widget.fahrenheit.nodeName).toEqual('INPUT'); 26 | expect(widget.fahrenheit.name).toEqual('fahrenheit'); 27 | }); 28 | 29 | it("can locate the celcius element", function() { 30 | expect(widget.celcius.nodeName).toEqual('SPAN'); 31 | expect(widget.celcius.className).toEqual('celcius'); 32 | }); 33 | 34 | it("can locate the convert button", function() { 35 | expect(widget.convertButton.nodeName).toEqual('INPUT'); 36 | expect(widget.convertButton.type).toEqual('submit'); 37 | }); 38 | 39 | it("can convert from fahrenheit to celcius", function() { 40 | widget.fahrenheit.value = "32"; 41 | widget.convert(); 42 | expect(widget.celcius.innerHTML).toEqual("0"); 43 | }); 44 | 45 | it("converts when we click the button", function() { 46 | // helper function to allow many similar tests in a row 47 | function setAndConvert(f) { 48 | widget.fahrenheit.value = f; 49 | widget.convertButton.click(); 50 | return widget.celcius.innerHTML; 51 | } 52 | expect(setAndConvert(32)).toEqual("0"); 53 | expect(setAndConvert(212)).toEqual("100"); 54 | expect(setAndConvert(20)).toEqual("-7"); 55 | expect(setAndConvert(40)).toEqual("4"); 56 | }); 57 | 58 | describe("color gradient", function() { 59 | var freezing = new Color(208, 230, 254); 60 | var boiling = new Color(255, 50, 65); 61 | 62 | it("calculates an appropriate color for when it's cold", function() { 63 | expect(widget.colorFor(0)).toEqual(freezing.toHex()); 64 | }); 65 | 66 | it("calculates an appropriate color for when it's hot", function() { 67 | expect(widget.colorFor(100)).toEqual(boiling.toHex()); 68 | }); 69 | 70 | it("sets the background color after conversion", function() { 71 | widget.fahrenheit.value = 212; 72 | widget.convertButton.click(); 73 | window.console.log(widget.widgetElement.style.backgroundColor); 74 | var bg = widget.widgetElement.style.backgroundColor; 75 | expect(bg).toEqual("rgb(255, 50, 65)"); // yes, it's a string :-( 76 | }); 77 | }); 78 | 79 | }); 80 | -------------------------------------------------------------------------------- /05_dom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test-First Teaching: learn_javascript: dom 4 | 5 | 6 | 7 |
8 |

TestFirst.org

9 |

the home of test-first teaching

10 |
11 | 24 |

dom

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /05_dom/numberSpec.js: -------------------------------------------------------------------------------- 1 | describe("Number", function() { 2 | describe("toHex", function() { 3 | it("can convert a number to its hex representation", function() { 4 | expect((0).toHex()).toEqual("00"); 5 | expect((1).toHex()).toEqual("01"); 6 | expect((10).toHex()).toEqual("0a"); 7 | expect((15).toHex()).toEqual("0f"); 8 | expect((16).toHex()).toEqual("10"); 9 | expect((255).toHex()).toEqual("ff"); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /05_dom/temperatureDemo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Temperature Widget Demo 6 | 7 | 8 | 9 | 10 | 15 | 16 | 28 | 29 | 30 |
31 |

Fahrenheit:

32 |

Celcius: ???

33 |

34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /06_bouncing_ball/bouncingBallSpec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Here we are going to create a JS widget that makes a div bounce around inside another div. 3 | Ultimately we will have many divs bouncing simultaneously; each will have its own object 4 | that describes where it is at any moment, as well as other properties such as its size 5 | and color. 6 | */ 7 | 8 | describe("BouncingBall", function() { 9 | var jasmineContent; 10 | var arena; 11 | var ball; 12 | 13 | beforeEach(function() { 14 | // set some fixture HTML inside the Jasmine runner page 15 | jasmineContent = document.getElementById("jasmine_content"); 16 | jasmineContent.innerHTML = "
"; 17 | // create a simple object for testing 18 | arena = document.getElementById("arena"); 19 | ball = new BouncingBall(arena); 20 | }); 21 | 22 | describe("when created", function() { 23 | it("contains an 'element' property", function() { 24 | expect(ball.div).not.toBeUndefined(); 25 | expect(ball.div instanceof HTMLElement).toBeTruthy(); 26 | }); 27 | 28 | it("is a child of the arena", function() { 29 | expect(ball.div.parentNode).toEqual(arena); 30 | }); 31 | 32 | it("has some default properties", function() { 33 | // see http://www.w3schools.com/jsref/dom_obj_style.asp#positioning 34 | expect(ball.height).toEqual(10); 35 | expect(ball.width).toEqual(10); 36 | expect(ball.x).toEqual(0); 37 | expect(ball.y).toEqual(0); 38 | expect(ball.color).toEqual("#FF0000"); 39 | }); 40 | 41 | it("sets those default properties on the div", function() { 42 | expect(ball.div.style.height).toEqual("10px"); 43 | expect(ball.div.style.width).toEqual("10px"); 44 | expect(ball.div.style.backgroundColor).toEqual("rgb(255, 0, 0)"); 45 | expect(ball.div.style.left).toEqual("0px"); 46 | expect(ball.div.style.top).toEqual("0px"); 47 | }); 48 | 49 | it("has a velocity"); 50 | 51 | it("moves"); 52 | 53 | it("sets a timeout to move again"); 54 | 55 | it("can be destroyed"); 56 | 57 | it("can be stopped"); 58 | 59 | it("can be started"); 60 | }) 61 | }); 62 | -------------------------------------------------------------------------------- /06_bouncing_ball/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test-First Teaching: learn_javascript: bouncing_ball 4 | 5 | 6 | 7 |
8 |

TestFirst.org

9 |

the home of test-first teaching

10 |
11 | 24 |

bouncing_ball

25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rake" 4 | gem "rspec", ">=2.0" 5 | gem "jasmine" 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | childprocess (0.3.8) 5 | ffi (~> 1.0, >= 1.0.11) 6 | diff-lcs (1.1.3) 7 | ffi (1.4.0) 8 | jasmine (1.3.1) 9 | jasmine-core (~> 1.3.1) 10 | rack (~> 1.0) 11 | rspec (>= 1.3.1) 12 | selenium-webdriver (>= 0.1.3) 13 | jasmine-core (1.3.1) 14 | multi_json (1.6.1) 15 | rack (1.5.2) 16 | rake (10.0.3) 17 | rspec (2.12.0) 18 | rspec-core (~> 2.12.0) 19 | rspec-expectations (~> 2.12.0) 20 | rspec-mocks (~> 2.12.0) 21 | rspec-core (2.12.2) 22 | rspec-expectations (2.12.1) 23 | diff-lcs (~> 1.1.3) 24 | rspec-mocks (2.12.2) 25 | rubyzip (0.9.9) 26 | selenium-webdriver (2.30.0) 27 | childprocess (>= 0.2.5) 28 | multi_json (~> 1.0) 29 | rubyzip 30 | websocket (~> 1.0.4) 31 | websocket (1.0.7) 32 | 33 | PLATFORMS 34 | ruby 35 | 36 | DEPENDENCIES 37 | jasmine 38 | rake 39 | rspec (>= 2.0) 40 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | Dir.chdir Rake.application.original_dir 2 | 3 | require 'jasmine' 4 | load 'jasmine/tasks/jasmine.rake' 5 | 6 | def config_file 7 | project_root = File.dirname(__FILE__) 8 | f = File.join(project_root, 'jasmine.yml') 9 | if File.exist?(f) 10 | f 11 | else 12 | File.join(project_root, '../jasmine.yml') 13 | end 14 | end 15 | 16 | Rake::Task['jasmine:server'].clear 17 | 18 | task "jasmine:server" => "jasmine:require" do 19 | port = ENV['JASMINE_PORT'] || 8888 20 | puts "your tests are here:" 21 | puts " http://localhost:#{port}/" 22 | Jasmine.load_configuration_from_yaml(config_file) 23 | app = Jasmine::Application.app(Jasmine.config) 24 | Jasmine::Server.new(port, app).start 25 | end 26 | 27 | task "default" => "jasmine:server" 28 | -------------------------------------------------------------------------------- /abstract.txt: -------------------------------------------------------------------------------- 1 | The target audience for this class are individuals with programming experience in another language and that understand web development. A book will be provided to compliment the class instruction. 2 | 3 | The class will cover: 4 | - Javascript language 5 | - TDD using Jasmine 6 | - Working with the browser DOM 7 | - AJAX 8 | - jQuery basics 9 | 10 | Alex Chaffee is an expert in object oriented programming and design, user interface design, website administration, and systems programming in Unix, MacOS, and Windows. Alex's many projects and roles can be viewed on his site, http://alexch.github.com/ . 11 | 12 | http://classes.blazingcloud.net/courses/show/18/javascript-for-programmers 13 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | /* Solarized Palette http://ethanschoonover.com/solarized */ 2 | /* Font Stacks http://www.artsiteframework.com/guide/fontstacks.php */ 3 | body { 4 | background-color: white; 5 | font-family: Optima, Candara, "Trebuchet MS", sans-serif; 6 | margin: 0px; 7 | font-size: 15px; 8 | line-height: 19px; } 9 | 10 | a, a:visited { 11 | color: #268bd2; } 12 | 13 | a:hover { 14 | text-decoration: underline; } 15 | 16 | a { 17 | text-decoration: none; } 18 | 19 | .content { 20 | padding: 0 18em; 21 | max-width: 44em; 22 | margin: 1em auto; 23 | min-height: 30em; } 24 | .content h1, .content h2, .content h3 { 25 | font-family: "Lucida Sans", "Lucida Grande", "Lucida Sans Unicode", Verdana, sans-serif; 26 | margin-left: -1em; } 27 | .content h1 { 28 | text-shadow: #999 1px 1px 1px; 29 | padding: .5em .25em; 30 | margin-top: 1em; } 31 | .content h2 { 32 | margin-top: 1em; 33 | border-bottom: #268bd2 1px dotted; } 34 | 35 | .header, .footer { 36 | font-size: 80%; 37 | padding: .25em; 38 | background-color: #f2f2f2; } 39 | 40 | .header { 41 | border-bottom: 1px solid #002b36; 42 | text-align: left; } 43 | .header h1 { 44 | color: #800000; 45 | margin: 0 0 -8px 0; 46 | padding: 0; 47 | font: 30px/48px Optima, Candara, "Trebuchet MS", sans-serif; 48 | letter-spacing: 0; } 49 | .header h2 { 50 | margin: 0 0 4px 13px; 51 | padding: 0; 52 | font: 10pt Optima, Candara, "Trebuchet MS", sans-serif; 53 | color: #686868; 54 | letter-spacing: 0; 55 | font-variant: small-caps; } 56 | .header a, .header a:visited { 57 | color: #800000; 58 | text-decoration: none; } 59 | .header a:hover { 60 | color: #37030B; 61 | text-decoration: none; } 62 | 63 | .footer { 64 | border-top: 1px solid #002b36; 65 | text-align: center; } 66 | 67 | .nav { 68 | float: left; 69 | margin: 1em; 70 | padding: 0 1em 1em; 71 | width: 14em; 72 | font-size: 75%; } 73 | 74 | .nav, .info { 75 | background: -webkit-gradient(linear, left bottom, left top, color-stop(0.15, white), color-stop(0.85, #f2f2f2)); 76 | background: -moz-linear-gradient(center bottom, white 15%, #f2f2f2 85%); 77 | -moz-border-radius: 10px; 78 | border-radius: 10px; } 79 | 80 | .info { 81 | float: right; 82 | margin: 1em; 83 | padding: 0 1em 1em; 84 | font-size: 75%; } 85 | 86 | pre { 87 | font-family: "Lucida Sans Typewriter Regular", "lucida console", monaco, "andale mono", "bitstream vera sans mono", consolas, "DejaVu Sans Mono", Courier, "Courier New", monospace; 88 | background-color: #e9e9ff; 89 | padding: .5em 1em; 90 | border: 1px solid #93a1a1; } 91 | 92 | code { 93 | font-family: "Lucida Sans Typewriter Regular", "lucida console", monaco, "andale mono", "bitstream vera sans mono", consolas, "DejaVu Sans Mono", Courier, "Courier New", monospace; 94 | background-color: #efefff; } 95 | 96 | pre > code { 97 | background-color: #e9e9ff; } 98 | 99 | li > p:nth-child(0) { 100 | margin: 0; } 101 | 102 | ul li ul li { 103 | margin-top: .1em; 104 | margin-bottom: 1em; } 105 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test-First Teaching: learn_javascript: learn_javascript 4 | 5 | 6 | 7 |
8 |

TestFirst.org

9 |

the home of test-first teaching

10 |
11 | 24 |

learn_javascript

25 |

Learn JavaScript Test-First

26 | 27 |

Setup

28 | 29 |

Welcome!

30 | 31 | 38 | 39 | 40 |

Install Ruby

41 | 42 |

First check if you have ruby already. Try this:

43 | 44 |
    ruby -v
 45 | 
46 | 47 |

If the response is something like this

48 | 49 |
    ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.7.0]
 50 | 
51 | 52 |

then skip ahead.

53 | 54 |

Otherwise...

55 | 56 | 60 | 61 | 62 |

Install Jasmine

63 | 64 |

Jasmine is a JavaScript testing framework. While Jasmine can be run from a plain HTML file, we are using the Jasmine Server, which comes as a "gem" (a Ruby program).

65 | 66 |
gem install jasmine
 67 | 
68 | 69 |

Learning

70 | 71 |

Your course directory has a list of lab directories. Each directory has a spec file. You will write all the code to make all the specs in it pass.

72 | 73 |

To get your feet wet in this process, go into the "hello" lab with cd 00_hello and read the detailed instructions in its index.html file.

74 | 75 |

Once you got through "hello", then congratulations! Now it's time to go to the 76 | next directory (whose name begins with 01_) and start learning JavaScript!

77 | 78 |

Problems? Questions?

79 | 80 |

First, ask your neighbor. Then, ask your instructor.

81 | 82 |

Then ask Google (seriously!). If there's an error, try copying the error string and pasting it into a Google search box. If that doesn't help, do a search on one of these sites:

83 | 84 | 87 | 88 | 89 |

You can also find help at the TestFirst.org site or the Test-First Teaching mailing list.

90 | 91 |

Resources

92 | 93 | 114 | 115 |
116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /jasmine.yml: -------------------------------------------------------------------------------- 1 | src_dir: . 2 | 3 | # all files, including specs, are included as src_files 4 | src_files: 5 | - '**/*.js' 6 | - '../helpers/**/*.js' 7 | - 'helpers/**/*.js' 8 | 9 | # in order to remove duplicates, specify a nonexistent dir for specs 10 | spec_dir: nada 11 | 12 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | * make git repo generator work 2 | * make 'rake jasmine:ci' work (may need to fix rspec gem version bug in Jasmine) 3 | * use custom runner 4 | * 'show passed' on by default 5 | * stop after first failure 6 | * figure out how to get common helpers (Jasmine doesn't like my file layout) 7 | 8 | --------------------------------------------------------------------------------