extending your tdd cycle into javascriptfiles.meetup.com/310796/tdd_javascript.pdf · 1 wednesday,...

Post on 22-Sep-2020

5 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Gregory Moeck

Extending Your TDD Cycle Into JavaScript

@gregmoeck

Wednesday, May 16, 12

Quick Poll

Wednesday, May 16, 12

Question1

Wednesday, May 16, 12

Practice TDD/BDD In Ruby/Rails

Wednesday, May 16, 12

Question2

Wednesday, May 16, 12

Practice TDD/BDD In JavaScript

Wednesday, May 16, 12

Why The Difference?

Wednesday, May 16, 12

Why Can’t I Test My JS?

Wednesday, May 16, 12

“All Of The Pain That We Feel When Writing Unit Tests

Points At Underlying Design Problems.

Michael Feathers, The Deep Synergy Between Good

Design and TestabilityWednesday, May 16, 12

“ In Test First Development, When We Feel Pain, We

Change Our Tests. In Test Driven Development When We Feel Pain, We Change

Our Architecture

Corey Haines, Fast Rails Tests

Wednesday, May 16, 12

Story Time

Wednesday, May 16, 12

Imagine A Newbie Has Heard About The

“Wonders” Of TDD, And Comes To You With A

Question:

Wednesday, May 16, 12

How Would I Test This?

Wednesday, May 16, 12

<?php...<div id= “vault_items”> ... $query1 = "SELECT * FROM storage_access_vault_items WHERE access_id = {$_GET[pid]}"; $result1 = mysql_query($query1); $inner_vault_items = array(); while($this_item = mysql_fetch_assoc($result1))   { ?> <div class= “vault_item”> <?= $this_item[‘description’] ?> ... </div><?php } ...</div>?>

Wednesday, May 16, 12

Summary

Wednesday, May 16, 12

Writing Code Is Easy

Wednesday, May 16, 12

Testing Well Architected

Code Is EasyWednesday, May 16, 12

Architecting Code Well Is

HARD!!!!Wednesday, May 16, 12

Architectural Patterns To Help Testing

Wednesday, May 16, 12

Ports &

AdaptersWednesday, May 16, 12

ApplicationDomain

Adapter

Port

Adapter

Port

Adapter

Port

Adapter

Port

Wednesday, May 16, 12

Key Idea:Isolate

Application Domain

Wednesday, May 16, 12

What Is A Port?

Wednesday, May 16, 12

An Interface To Technical

InfrastructureWednesday, May 16, 12

Examples:

Wednesday, May 16, 12

Examples:DOM

Wednesday, May 16, 12

Examples:DOM

XMLHTTPRequest

Wednesday, May 16, 12

Examples:DOM

XMLHTTPRequest

WEBDB

Wednesday, May 16, 12

Examples:DOM

XMLHTTPRequest

WEBDB

Websockets

Wednesday, May 16, 12

Examples:DOM

XMLHTTPRequest

WEBDB

Websockets

IndexedDB

Wednesday, May 16, 12

Examples:DOM

XMLHTTPRequest

WEBDB

Websockets

IndexedDB

LocalStorage

Wednesday, May 16, 12

Examples:DOM

XMLHTTPRequest

WEBDB

Websockets

IndexedDB

LocalStorage

Etc...

Wednesday, May 16, 12

What Is An Adapter?

Wednesday, May 16, 12

An Interface To Translate Domain Into

TechnicalWednesday, May 16, 12

Example:

DOMBoard

Renderer

renderBoard(numberOfRows, numberOfColumns)

markCell(row, column, marker)

Wednesday, May 16, 12

Why Is This Useful?

Wednesday, May 16, 12

Isolates From

Technical Concerns

Wednesday, May 16, 12

No Browser Dependency

Wednesday, May 16, 12

Side Note:

Wednesday, May 16, 12

Wednesday, May 16, 12

Unit Tests

Wednesday, May 16, 12

Unit Tests

Wednesday, May 16, 12

Unit Tests Node

Wednesday, May 16, 12

Unit Tests NodeIntegration

Tests

Wednesday, May 16, 12

Unit Tests NodeIntegration

Tests

Wednesday, May 16, 12

Unit Tests NodeIntegration

Tests Browser

Wednesday, May 16, 12

Unit Tests NodeIntegration

Tests Browser

Acceptance Tests

Wednesday, May 16, 12

Unit Tests NodeIntegration

Tests Browser

Acceptance Tests

Wednesday, May 16, 12

Unit Tests NodeIntegration

Tests Browser

Acceptance Tests Webdriver

Wednesday, May 16, 12

Manage Your

Objects Peers

Wednesday, May 16, 12

Three Types Of Peers

Wednesday, May 16, 12

Dependencies

Wednesday, May 16, 12

Service The Object

NEEDS To Do Its Job

Wednesday, May 16, 12

Example:

PlayerPieces

Tracker

markCell(row, column, marker)

BoardView

Wednesday, May 16, 12

Should Pass Dependency

Into The Constructor

Wednesday, May 16, 12

Notifications

Wednesday, May 16, 12

Peers That Need To Be Kept Up To

DateWednesday, May 16, 12

Example:

Turn Tracker

newPlayersTurn(player)

TurnTrackerListener

Wednesday, May 16, 12

Dynamically Add

Listeners At Run Time

Wednesday, May 16, 12

Adjustments / Strategies

Wednesday, May 16, 12

Example:

EventDispatcher

receiveCell(cell)

CurrentPlayer

Wednesday, May 16, 12

Behavior Needs To Be Changeable At Runtime

Wednesday, May 16, 12

Structural Example

Wednesday, May 16, 12

XO

OO

XX X

Tic-Tac-Toe

Wednesday, May 16, 12

Emergent Design, So

Not Perfectly Architected

Wednesday, May 16, 12

Https://github.com/gmoeck/tic-tac-toe

Wednesday, May 16, 12

Basic Tooling

Wednesday, May 16, 12

CONFIG = ENV['CONFIG'] || 'Debug'

require 'rack'

namespace :test do

desc "Run all acceptance tests"

task :acceptance do

system("rspec spec/acceptance/end_to_end.rb --color")

end

desc "Run all unit tests"

task :unit do

system("node node_modules/jasmine-node/lib/jasmine-node/cli.js spec/unit --color")

end

desc "Run all integration tests"

task :integration do

File.delete("spec/integration/runner/all_tests.js") if File.file?("spec/integration/runner/all_tests.js")

File.open("spec/integration/runner/all_tests.js", 'w') do |f|

Dir.glob("spec/integration/*_spec.js").each do |file_name|

file_name = file_name.slice(/[a-zA-z]*_spec/)

f.write("require('#{file_name}');\n")

end

end

system("./bin/build_integration_tests.js")

system("open spec/integration/runner/runner.html")

end

task :all => [:unit, :acceptance]

end

desc "Debug the application"

task :debug do

system("./bin/build_application.js")

sleep 1

system("open public/index.html")

end

desc "Run a server for prototyping"

task :server do

Rack::Handler::Thin.run Rack::Directory.new(File.expand_path('public')), :Port => 3000

end

Wednesday, May 16, 12

Summary:

Wednesday, May 16, 12

Summary:Uses Modulr For Package Dependencies

Wednesday, May 16, 12

Summary:Rake Test:[unit,integration,acceptance]

Uses Modulr For Package Dependencies

Wednesday, May 16, 12

Summary:Rake Test:[unit,integration,acceptance]

Rake Debug

Uses Modulr For Package Dependencies

Wednesday, May 16, 12

First End-To-End

TestWednesday, May 16, 12

describe "end to end acceptance test", :type => :request do  before(:each) do    @application = TicTacToeApplicationDriver.new    @application.start  end

  it "marks the board" do    @application.mark_board(1,1)    @application.shows_board(      [        [' ',' ',' '],        [' ','X',' '],        [' ',' ',' ']      ]    )  endend

Wednesday, May 16, 12

class TicTacToeApplicationDriver  include Capybara::DSL  include Capybara::RSpecMatchers  APPLICATION_PORT = 1234  def initialize    @application_server = ApplicationServer.new  end  def start    @application_server.start    visit "http://localhost:#{APPLICATION_PORT}/index.html"  end  def mark_board(x,y)    cell_at(x,y).click  end  def shows_board(board)    board.each_index do |row|      board[row].each_index do |column|        if board[row][column] != ' '          cell_at(column, row).text.should == board[row][column]        end      end    end  end

private def cell_at(x,y)    find("[data-board-x='#{x}'][data-board-y='#{y}']")  endend

Wednesday, May 16, 12

Failure/Error: @application.mark_board(0,0)

Capybara::ElementNotFound:

Unable to find css "[data-board-x='0'][data-board-y='0']"

Wednesday, May 16, 12

rake test:acceptanceFailure/Error: @application.mark_board(0,0)

Capybara::ElementNotFound:

Unable to find css "[data-board-x='0'][data-board-y='0']"

Wednesday, May 16, 12

var DOMBoardView = require('./ui/dom_board_view').DOMBoardView;

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView(); boardView.renderBoard(3,3);});

Wednesday, May 16, 12

var DOMBoardView = require('./ui/dom_board_view').DOMBoardView;

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView(); boardView.renderBoard(3,3);});

Wednesday, May 16, 12

var DOMBoardView = require('./ui/dom_board_view').DOMBoardView;

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView(); boardView.renderBoard(3,3);});

main.js

Wednesday, May 16, 12

var DOMBoardView = require('ui/dom_board_view').DOMBoardView;

describe('DOMBoardView', function() {  var view;  beforeEach(function() {    view = new BoardView(); view.renderBoard(3,3);  });

  afterEach(function() {    view.remove();  });

  it('renders the proper number of cells', function() {    expect(document.querySelectorAll('[data-board-x][data-board-y]').length).toBe(9);  }); });

Wednesday, May 16, 12

var DOMBoardView = require('ui/dom_board_view').DOMBoardView;

describe('DOMBoardView', function() {  var view;  beforeEach(function() {    view = new BoardView(); view.renderBoard(3,3);  });

  afterEach(function() {    view.remove();  });

  it('renders the proper number of cells', function() {    expect(document.querySelectorAll('[data-board-x][data-board-y]').length).toBe(9);  }); });

spec/integration/dom_board_view_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:integration

Failure

Wednesday, May 16, 12

var DOMBoardView = function() {};

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); document.body.appendChild(cell); } } }};

Wednesday, May 16, 12

var DOMBoardView = function() {};

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); document.body.appendChild(cell); } } }};

src/ui/dom_board_view.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:integration

Passed

Wednesday, May 16, 12

Failure/Error: @application.shows_board(

expected: "X"

got: "" (using ==)

Wednesday, May 16, 12

rake test:acceptance Failure/Error: @application.shows_board(

expected: "X"

got: "" (using ==)

Wednesday, May 16, 12

var DOMBoardView = require('ui/dom_board_view').DOMBoardView;var fireEvent = require('./test_helpers').fireEvent;

describe('DOMBoardView', function() { ... describe('when clicking on a cell', function() {    it('marks the cell with "X" when clicked', function() {      fireEvent(document.querySelector('[data-board-x="1"][data-board-y="1"]'), 'click');

      expect(document.querySelector('[data-board-x="1"][data-board-y="1"]').innerText).toEqual('X');    });  });});

Wednesday, May 16, 12

var DOMBoardView = require('ui/dom_board_view').DOMBoardView;var fireEvent = require('./test_helpers').fireEvent;

describe('DOMBoardView', function() { ... describe('when clicking on a cell', function() {    it('marks the cell with "X" when clicked', function() {      fireEvent(document.querySelector('[data-board-x="1"][data-board-y="1"]'), 'click');

      expect(document.querySelector('[data-board-x="1"][data-board-y="1"]').innerText).toEqual('X');    });  });});

spec/integration/dom_board_view_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:integration

Failure

Wednesday, May 16, 12

var DOMBoardView = function() {};

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); document.body.appendChild(cell); } } }};

Wednesday, May 16, 12

var DOMBoardView = function() {};

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); document.body.appendChild(cell); } } }};

src/ui/dom_board_view.js

Wednesday, May 16, 12

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); cell.addEventListener('click', this._cellClicked.bind(this, i, j)); document.body.appendChild(cell); } } }, _cellClicked: function(row, column) { var cell = document.querySelector( '[data-board-x="' + column + '"]' +                            '[data-board-y="' + row + '"]');    cell.innerText = 'X'; }};

Wednesday, May 16, 12

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); cell.addEventListener('click', this._cellClicked.bind(this, i, j)); document.body.appendChild(cell); } } }, _cellClicked: function(row, column) { var cell = document.querySelector( '[data-board-x="' + column + '"]' +                            '[data-board-y="' + row + '"]');    cell.innerText = 'X'; }};

src/ui/dom_board_view.js

Wednesday, May 16, 12

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); cell.addEventListener('click', this._cellClicked.bind(this, i, j)); document.body.appendChild(cell); } } }, _cellClicked: function(row, column) { var cell = document.querySelector( '[data-board-x="' + column + '"]' +                            '[data-board-y="' + row + '"]');    cell.innerText = 'X'; }};

Wednesday, May 16, 12

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); cell.addEventListener('click', this._cellClicked.bind(this, i, j)); document.body.appendChild(cell); } } }, _cellClicked: function(row, column) { var cell = document.querySelector( '[data-board-x="' + column + '"]' +                            '[data-board-y="' + row + '"]');    cell.innerText = 'X'; }};

src/ui/dom_board_view.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:integration

Passed

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:acceptance

Passed

Wednesday, May 16, 12

Refactor To Add Style In Rendering

Wednesday, May 16, 12

...

Wednesday, May 16, 12

Refactor To Be True Adapter

Wednesday, May 16, 12

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); cell.addEventListener('click', this._cellClicked.bind(this, i, j)); document.body.appendChild(cell); } } }, _cellClicked: function(row, column) { var cell = document.querySelector( '[data-board-x="' + column + '"]' +                            '[data-board-y="' + row + '"]');    cell.innerText = 'X'; }};

Wednesday, May 16, 12

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); cell.addEventListener('click', this._cellClicked.bind(this, i, j)); document.body.appendChild(cell); } } }, _cellClicked: function(row, column) { var cell = document.querySelector( '[data-board-x="' + column + '"]' +                            '[data-board-y="' + row + '"]');    cell.innerText = 'X'; }};

src/ui/dom_board_view.js

Wednesday, May 16, 12

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); cell.addEventListener('click', this._cellClicked.bind(this, i, j)); document.body.appendChild(cell); } } }, _cellClicked: function(row, column) { var cell = document.querySelector( '[data-board-x="' + column + '"]' +                            '[data-board-y="' + row + '"]');    cell.innerText = 'X'; }};

Wednesday, May 16, 12

DOMBoardView.prototype = { renderBoard: function(rows, columns) { for(var i = 0; i < rows; i++) { for(var j = 0; j < columns; i++) { var cell = document.createElement('div'); cell.setAttribute('data-board-x', j); cell.setAttribute('data-board-y', i); cell.addEventListener('click', this._cellClicked.bind(this, i, j)); document.body.appendChild(cell); } } }, _cellClicked: function(row, column) { var cell = document.querySelector( '[data-board-x="' + column + '"]' +                            '[data-board-y="' + row + '"]');    cell.innerText = 'X'; }};

src/ui/dom_board_view.js

Wednesday, May 16, 12

describe('DOMBoardView', function() { ... describe('when clicking on a cell', function() {    it('marks the cell with "X" when clicked', function() { var listener = {        cellSelected: jasmine.createSpy('listener#cellSelected')      };      view.addListener('cellSelected', listener);

      fireEvent(document.querySelector('[data-board-x="1"][data-board-y="1"]'), 'click');

      expect(listener.cellSelected).toHaveBeenCalledWith({ row: 1, column: 1 }); });  });});

Wednesday, May 16, 12

describe('DOMBoardView', function() { ... describe('when clicking on a cell', function() {    it('marks the cell with "X" when clicked', function() { var listener = {        cellSelected: jasmine.createSpy('listener#cellSelected')      };      view.addListener('cellSelected', listener);

      fireEvent(document.querySelector('[data-board-x="1"][data-board-y="1"]'), 'click');

      expect(listener.cellSelected).toHaveBeenCalledWith({ row: 1, column: 1 }); });  });});

spec/integration/dom_board_view_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:integration

Failure

Wednesday, May 16, 12

DOMBoardView.prototype = { ... addListener: function(event, listener) { this._listener = listener; },

_cellClicked: function(row, column) { this._listener.cellSelected(row, column); }};

Wednesday, May 16, 12

DOMBoardView.prototype = { ... addListener: function(event, listener) { this._listener = listener; },

_cellClicked: function(row, column) { this._listener.cellSelected(row, column); }};

src/ui/dom_board_view.js

Wednesday, May 16, 12

Failure/Error: @application.shows_board(

expected: "X"

got: "" (using ==)

Wednesday, May 16, 12

rake test:acceptance Failure/Error: @application.shows_board(

expected: "X"

got: "" (using ==)

Wednesday, May 16, 12

var DOMBoardView = require('./ui/dom_board_view').DOMBoardView;

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView(); var eventHandler = { cellSelected: function(row, column) { boardView.markCell(row, column, ‘X’); } }; boardView.addListener(‘cellSelected’, eventHandler); boardView.renderBoard(3,3);});

Wednesday, May 16, 12

var DOMBoardView = require('./ui/dom_board_view').DOMBoardView;

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView(); var eventHandler = { cellSelected: function(row, column) { boardView.markCell(row, column, ‘X’); } }; boardView.addListener(‘cellSelected’, eventHandler); boardView.renderBoard(3,3);});

Wednesday, May 16, 12

var DOMBoardView = require('./ui/dom_board_view').DOMBoardView;

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView(); var eventHandler = { cellSelected: function(row, column) { boardView.markCell(row, column, ‘X’); } }; boardView.addListener(‘cellSelected’, eventHandler); boardView.renderBoard(3,3);});

main.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:acceptance

Passed

Wednesday, May 16, 12

DOMBoardView

Main

cellSelected

DO

MBo

ardV

iewLis

tene

r

markCell

Wednesday, May 16, 12

Mark Multiple

CellsWednesday, May 16, 12

DOMBoardView

Main

cellSelected

DO

MBo

ardV

iewLis

tene

r

markCell

TurnTracker

newPlayersTurn

TurnTrackerListener

Wednesday, May 16, 12

var DOMBoardView = require('./ui/dom_board_view').DOMBoardView;var TurnTracker = require('./turn_tracker').TurnTracker;

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView(); var turnTracker = new TurnTracker(‘X’, ‘O’); var eventHandler = { cellSelected: function(row, column) { boardView.markCell(row, column, ‘X’); turnTracker.playerOwnsNewCell({row: row, column: column}); } }; turnTracker.addListener(‘newPlayersTurn’, eventHandler); boardView.addListener(‘cellSelected’, eventHandler);

turnTracker.startNewGame(); boardView.renderBoard(3,3);});

Wednesday, May 16, 12

var DOMBoardView = require('./ui/dom_board_view').DOMBoardView;var TurnTracker = require('./turn_tracker').TurnTracker;

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView(); var turnTracker = new TurnTracker(‘X’, ‘O’); var eventHandler = { cellSelected: function(row, column) { boardView.markCell(row, column, ‘X’); turnTracker.playerOwnsNewCell({row: row, column: column}); } }; turnTracker.addListener(‘newPlayersTurn’, eventHandler); boardView.addListener(‘cellSelected’, eventHandler);

turnTracker.startNewGame(); boardView.renderBoard(3,3);});

main.js

Wednesday, May 16, 12

var TurnTracker = require('../../src/turn_tracker').TurnTracker;

describe('TurnTracker', function() { it('notifies its listeners that it is the first players turn when told to start a new game', function() { var player1 = {player: 1}, player2 = {player:2};    var turnTracker = new TurnTracker(player1, player2); var listener = { newPlayersTurn: jasmine.createSpy(‘listener#newPlayersTurn’) }; turnTracker.addListener(‘newPlayersTurn’, listener); turnTracker.startNewGame();

    expect(listener.newPlayersTurn).toHaveBeenCalledWith( player1);

  });});

Wednesday, May 16, 12

var TurnTracker = require('../../src/turn_tracker').TurnTracker;

describe('TurnTracker', function() { it('notifies its listeners that it is the first players turn when told to start a new game', function() { var player1 = {player: 1}, player2 = {player:2};    var turnTracker = new TurnTracker(player1, player2); var listener = { newPlayersTurn: jasmine.createSpy(‘listener#newPlayersTurn’) }; turnTracker.addListener(‘newPlayersTurn’, listener); turnTracker.startNewGame();

    expect(listener.newPlayersTurn).toHaveBeenCalledWith( player1);

  });});

spec/unit/turn_tracker_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

var TurnTracker = function(player1, player2) { this._player1 = player1; this._player2 = player2;};

TurnTracker.prototype = { addListener: function(event, listener) { this._listener = listener; },

startNewGame: function() { this._listener.newPlayersTurn(player1); }};

Wednesday, May 16, 12

var TurnTracker = function(player1, player2) { this._player1 = player1; this._player2 = player2;};

TurnTracker.prototype = { addListener: function(event, listener) { this._listener = listener; },

startNewGame: function() { this._listener.newPlayersTurn(player1); }};

src/turn_tracker.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

Duplicate Code For

EventsWednesday, May 16, 12

var Announce = require('../../src/util/announcer').Announcer;

describe('Announcer', function() { var announcer, listener;  beforeEach(function() {    announcer = new Announcer();    listener = {      someEvent: jasmine.createSpy('listener#someEvent')    };  });

it('notifies its listeners when an event they are registered for happens', function() {    announcer.addListener('someEvent', listener);

    announcer.announce('someEvent', 'abc');

    expect(listener.someEvent).toHaveBeenCalledWith('abc');  });});

Wednesday, May 16, 12

var Announce = require('../../src/util/announcer').Announcer;

describe('Announcer', function() { var announcer, listener;  beforeEach(function() {    announcer = new Announcer();    listener = {      someEvent: jasmine.createSpy('listener#someEvent')    };  });

it('notifies its listeners when an event they are registered for happens', function() {    announcer.addListener('someEvent', listener);

    announcer.announce('someEvent', 'abc');

    expect(listener.someEvent).toHaveBeenCalledWith('abc');  });});

spec/unit/announcer_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

var Announcer = function() {};

Announcer.prototype = { addListener: function(event, listener) { this._listener = listener; },

announce: function(event, data) { this._listener.someEvent(data); }};

Wednesday, May 16, 12

var Announcer = function() {};

Announcer.prototype = { addListener: function(event, listener) { this._listener = listener; },

announce: function(event, data) { this._listener.someEvent(data); }};

src/util/announcer.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

var Announce = require('../../src/util/announcer').Announcer;

describe('Announcer', function() { ... it('does not notify its listeners when an event they are not registered for happens', function() {    announcer.addListener('anotherEvent', listener);

    announcer.announce('someEvent', 'abc');

    expect(listener.someEvent).not.toHaveBeenCalled();  });});

Wednesday, May 16, 12

var Announce = require('../../src/util/announcer').Announcer;

describe('Announcer', function() { ... it('does not notify its listeners when an event they are not registered for happens', function() {    announcer.addListener('anotherEvent', listener);

    announcer.announce('someEvent', 'abc');

    expect(listener.someEvent).not.toHaveBeenCalled();  });});

spec/unit/announcer_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

var Announcer = function() {};

Announcer.prototype = { addListener: function(event, listener) { this._listener = listener; this._event = event; },

announce: function(event, data) { if (this._event === event) { this._listener.someEvent(data); } }};

Wednesday, May 16, 12

var Announcer = function() {};

Announcer.prototype = { addListener: function(event, listener) { this._listener = listener; this._event = event; },

announce: function(event, data) { if (this._event === event) { this._listener.someEvent(data); } }};

src/util/announcer.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

var Announce = require('../../src/util/announcer').Announcer;

describe('Announcer', function() { ... it('notifies multiple listeners when an event they are registered for happens', function() {    var listener2 = {      anotherEvent: jasmine.createSpy('listener2#anotherEvent')    };

    announcer.addListener('anotherEvent', listener);    announcer.addListener('anotherEvent', listener2);    announcer.announce('anotherEvent', 'abc');

    expect(listener.someEvent).toHaveBeenCalledWith('abc');    expect(listener2.someEvent).toHaveBeenCalledWith('abc');  });});

Wednesday, May 16, 12

var Announce = require('../../src/util/announcer').Announcer;

describe('Announcer', function() { ... it('notifies multiple listeners when an event they are registered for happens', function() {    var listener2 = {      anotherEvent: jasmine.createSpy('listener2#anotherEvent')    };

    announcer.addListener('anotherEvent', listener);    announcer.addListener('anotherEvent', listener2);    announcer.announce('anotherEvent', 'abc');

    expect(listener.someEvent).toHaveBeenCalledWith('abc');    expect(listener2.someEvent).toHaveBeenCalledWith('abc');  });});

spec/unit/announcer_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

var Announcer = function() {  this._events = {};};

Announcer.prototype = {  addListener: function(event, listener) {    this._events[event] = this._events[event] || [];    this._events[event].push(listener);  },

  announce: function(event, data) {    var registeredListeners = this._events[event] || [];    registeredListeners.forEach(function(listener) {      listener[event](data);    });  }};

Wednesday, May 16, 12

var Announcer = function() {  this._events = {};};

Announcer.prototype = {  addListener: function(event, listener) {    this._events[event] = this._events[event] || [];    this._events[event].push(listener);  },

  announce: function(event, data) {    var registeredListeners = this._events[event] || [];    registeredListeners.forEach(function(listener) {      listener[event](data);    });  }};

src/util/announcer.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

var Announcer = require('./util/announcer').Announcer;var TurnTracker = function(player1, player2) { this._player1 = player1; this._player2 = player2; this._announcer = new Announcer();};

TurnTracker.prototype = { addListener: function(event, listener) { this._announcer.addListener(event, listener); },

startNewGame: function() { this._announcer.announce('newPlayersTurn', this._player1); }};

Wednesday, May 16, 12

var Announcer = require('./util/announcer').Announcer;var TurnTracker = function(player1, player2) { this._player1 = player1; this._player2 = player2; this._announcer = new Announcer();};

TurnTracker.prototype = { addListener: function(event, listener) { this._announcer.addListener(event, listener); },

startNewGame: function() { this._announcer.announce('newPlayersTurn', this._player1); }};

src/turn_tracker.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

Same With DOM Board

ViewWednesday, May 16, 12

var TurnTracker = require('../../src/turn_tracker').TurnTracker;

describe('TurnTracker', function() { ... it('notifies its listeners that it is the next players turn when told to that a player now owns a new cell', function() {    turnTracker.startNewGame();    listener.newPlayersTurn.reset();

    turnTracker.playerOwnsNewCell({row: 1, column: 1});

    expect(listener.newPlayersTurn).toHaveBeenCalledWith( player2);  });});

Wednesday, May 16, 12

var TurnTracker = require('../../src/turn_tracker').TurnTracker;

describe('TurnTracker', function() { ... it('notifies its listeners that it is the next players turn when told to that a player now owns a new cell', function() {    turnTracker.startNewGame();    listener.newPlayersTurn.reset();

    turnTracker.playerOwnsNewCell({row: 1, column: 1});

    expect(listener.newPlayersTurn).toHaveBeenCalledWith( player2);  });});

spec/unit/turn_tracker_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

With Some Refactoring

Wednesday, May 16, 12

var STATES = {  PLAYER1_TURN: {    playerOwnsNewCell: function(newCellInformation) {      this._announcer.announce('newPlayersTurn', this._player2);      this._currentState = STATES.PLAYER2_TURN;    }  },

  PLAYER2_TURN: {    playerOwnsNewCell: function(newCellInformation) {      this._announcer.announce('newPlayersTurn', this._player1);      this._currentState = STATES.PLAYER1_TURN;    }  }};

var TurnTracker = function(player1, player2) {  this._announcer = new Announcer();  this._player1 = player1;  this._player2 = player2;  this._currentState = STATES.PLAYER1_TURN;};

TurnTracker.prototype = {  addListener: function(event, listener) {    this._announcer.addListener(event, listener);  },

  playerOwnsNewCell: function(newCellInformation) {    this._currentState.playerOwnsNewCell.call(this, newCellInformation);  },

  startNewGame: function() {    this._announcer.announce('newPlayersTurn', this._player1);    this._currentState = STATES.PLAYER1_TURN;  }};

Wednesday, May 16, 12

var STATES = {  PLAYER1_TURN: {    playerOwnsNewCell: function(newCellInformation) {      this._announcer.announce('newPlayersTurn', this._player2);      this._currentState = STATES.PLAYER2_TURN;    }  },

  PLAYER2_TURN: {    playerOwnsNewCell: function(newCellInformation) {      this._announcer.announce('newPlayersTurn', this._player1);      this._currentState = STATES.PLAYER1_TURN;    }  }};

var TurnTracker = function(player1, player2) {  this._announcer = new Announcer();  this._player1 = player1;  this._player2 = player2;  this._currentState = STATES.PLAYER1_TURN;};

TurnTracker.prototype = {  addListener: function(event, listener) {    this._announcer.addListener(event, listener);  },

  playerOwnsNewCell: function(newCellInformation) {    this._currentState.playerOwnsNewCell.call(this, newCellInformation);  },

  startNewGame: function() {    this._announcer.announce('newPlayersTurn', this._player1);    this._currentState = STATES.PLAYER1_TURN;  }};

src/turn_tracker.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:acceptance

Passed

Wednesday, May 16, 12

Show And Track Victory

Wednesday, May 16, 12

describe "end to end acceptance test", :type => :request do ...  it "marks the board" do ...    @application.mark_board(1,0)    @application.shows_board(      [        ['X','X',' '],        ['O','O',' '],        [' ',' ',' ']      ]    ) @application.mark_board(0,2)    @application.shows_board(      [        ['X','X','X'],        ['O','O',' '],        [' ',' ',' ']      ]    ) @application.shows_player1_won  endend

Wednesday, May 16, 12

Failure/Error: @application.shows_player1_won

Wednesday, May 16, 12

rake test:acceptance

Failure/Error: @application.shows_player1_won

Wednesday, May 16, 12

DOMBoardView

Main

cellSelected

DO

MBo

ardV

iewLis

tene

r

markCell

TurnTracker

newPlayersTurn

TurnTrackerListener

playerOwnsNewCell

Wednesday, May 16, 12

DOMBoardView

Main

TurnTracker

PlayerplayerOwnsNewCell

markCell

DOMAlert

RenderershowPlayerWonGame

receiveCell

Wednesday, May 16, 12

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView();  var player1 = new Player('X', boardView, alertRenderer);  var player2 = new Player('O', boardView, alertRenderer);  var turnTracker = new TurnTracker(player1, player2);  var eventHandler = { cellSelected: function(cellInformation) { this._currentPlayer.receiveCell(cellInformation); }, newPlayersTurn: function(player) { this._currentPlayer = player; } }  var alertRenderer = new DOMAlertRenderer();

  turnTracker.addListener('newPlayersTurn', eventHandler);  boardView.addListener('cellSelected', eventHandler);  player1.addListener('playerOwnsNewCell', turnTracker);  player2.addListener('playerOwnsNewCell', turnTracker);

  turnTracker.startNewGame();  boardView.renderBoard(3, 3);});

Wednesday, May 16, 12

document.addEventListener("DOMContentLoaded", function() {  var boardView = new DOMBoardView();  var player1 = new Player('X', boardView, alertRenderer);  var player2 = new Player('O', boardView, alertRenderer);  var turnTracker = new TurnTracker(player1, player2);  var eventHandler = { cellSelected: function(cellInformation) { this._currentPlayer.receiveCell(cellInformation); }, newPlayersTurn: function(player) { this._currentPlayer = player; } }  var alertRenderer = new DOMAlertRenderer();

  turnTracker.addListener('newPlayersTurn', eventHandler);  boardView.addListener('cellSelected', eventHandler);  player1.addListener('playerOwnsNewCell', turnTracker);  player2.addListener('playerOwnsNewCell', turnTracker);

  turnTracker.startNewGame();  boardView.renderBoard(3, 3);});

main.js

Wednesday, May 16, 12

describe('Player', function() {  var player, board, listener;  beforeEach(function() {    board = new BoardRole();    player = new Player('X', board);    listener = new PlayerListenerRole();    player.addListener('playerOwnsNewCell', listener);  });

  describe('when it is told that to receive a cell', function() {    beforeEach(function() {      player.receiveCell({row: 2, column: 2});    });

    it('tells its board to mark that square with its marker', function() {      expect(board.markCell).toHaveBeenCalledWith(2,2,'X');    });  });

Wednesday, May 16, 12

describe('Player', function() {  var player, board, listener;  beforeEach(function() {    board = new BoardRole();    player = new Player('X', board);    listener = new PlayerListenerRole();    player.addListener('playerOwnsNewCell', listener);  });

  describe('when it is told that to receive a cell', function() {    beforeEach(function() {      player.receiveCell({row: 2, column: 2});    });

    it('tells its board to mark that square with its marker', function() {      expect(board.markCell).toHaveBeenCalledWith(2,2,'X');    });  });

spec/unit/player_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

var Player = function(marker, board, alertRenderer) { this._marker = marker; this._board = board;};

Player.prototype = { receiveCell: function(cellInformation) { this._board.markCell(cellInformation.row, cellInformation.column, this._marker); }};

Wednesday, May 16, 12

var Player = function(marker, board, alertRenderer) { this._marker = marker; this._board = board;};

Player.prototype = { receiveCell: function(cellInformation) { this._board.markCell(cellInformation.row, cellInformation.column, this._marker); }};

src/player.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

describe('Player', function() {  var player, board, listener;  beforeEach(function() {    board = new BoardRole();    player = new Player('X', board);    listener = new PlayerListenerRole();    player.addListener('playerOwnsNewCell', listener);  });

  describe('when it is told that to receive a cell', function() {    beforeEach(function() {      player.receiveCell({row: 2, column: 2});    });

    it('tells its board to mark that square with its marker', function() {      expect(board.markCell).toHaveBeenCalledWith(2,2,'X');    });

    it('notifies its listeners that it owns the cell that was selected', function() {      expect(listener.playerOwnsNewCell).toHaveBeenCalledWith( {row: 2, column: 2});    }); ...  });

Wednesday, May 16, 12

describe('Player', function() {  var player, board, listener;  beforeEach(function() {    board = new BoardRole();    player = new Player('X', board);    listener = new PlayerListenerRole();    player.addListener('playerOwnsNewCell', listener);  });

  describe('when it is told that to receive a cell', function() {    beforeEach(function() {      player.receiveCell({row: 2, column: 2});    });

    it('tells its board to mark that square with its marker', function() {      expect(board.markCell).toHaveBeenCalledWith(2,2,'X');    });

    it('notifies its listeners that it owns the cell that was selected', function() {      expect(listener.playerOwnsNewCell).toHaveBeenCalledWith( {row: 2, column: 2});    }); ...  });

spec/unit/player_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

var Player = function(marker, board, alertRenderer) { this._marker = marker; this._board = board; this._announcer = new Announcer();};

Player.prototype = { receiveCell: function(cellInformation) { this._board.markCell(cellInformation.row, cellInformation.column, this._marker); this._announcer.announce('playerOwnsNewCell’, cellInformation); }};

Wednesday, May 16, 12

var Player = function(marker, board, alertRenderer) { this._marker = marker; this._board = board; this._announcer = new Announcer();};

Player.prototype = { receiveCell: function(cellInformation) { this._board.markCell(cellInformation.row, cellInformation.column, this._marker); this._announcer.announce('playerOwnsNewCell’, cellInformation); }};

src/player.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

describe('Player', function() { ...  it('notifies tells its alerter that it has one the game when it receives all the cells in a row', function() { player.receiveCell({row: 0, column: 0}); player.receiveCell({row: 0, column: 1}); player.receiveCell({row: 0, column: 2}); expect(alerter.playerWonGame).toHaveBeenCalledWith({player: 'X'});  });});

Wednesday, May 16, 12

describe('Player', function() { ...  it('notifies tells its alerter that it has one the game when it receives all the cells in a row', function() { player.receiveCell({row: 0, column: 0}); player.receiveCell({row: 0, column: 1}); player.receiveCell({row: 0, column: 2}); expect(alerter.playerWonGame).toHaveBeenCalledWith({player: 'X'});  });});

spec/unit/player_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

With Some Refactoring

Wednesday, May 16, 12

var Player = function(marker, board, alertRenderer) { this._marker = marker; this._board = board; this._alerter = alertRenderer; this._announcer = new Announcer(); this._rows = []; for(var i = 0; i < 3; i++) { this._rows[i] = new CellTracker(3); }};

Player.prototype = { receiveCell: function(cellInformation) { this._board.markCell(cellInformation.row, cellInformation.column, this._marker); this._announcer.announce('playerOwnsNewCell’, cellInformation); this._rows[cellInformation.row].takeCell(); if (this._hasWonGame(cellInformation)) { this._alerter.playerWonGame({player: this._marker}); } }};...

Wednesday, May 16, 12

var Player = function(marker, board, alertRenderer) { this._marker = marker; this._board = board; this._alerter = alertRenderer; this._announcer = new Announcer(); this._rows = []; for(var i = 0; i < 3; i++) { this._rows[i] = new CellTracker(3); }};

Player.prototype = { receiveCell: function(cellInformation) { this._board.markCell(cellInformation.row, cellInformation.column, this._marker); this._announcer.announce('playerOwnsNewCell’, cellInformation); this._rows[cellInformation.row].takeCell(); if (this._hasWonGame(cellInformation)) { this._alerter.playerWonGame({player: this._marker}); } }};...

src/player.js

Wednesday, May 16, 12

var CellTracker = function(totalCells) { this._remainingCells = totalCells;};

CellTracker.prototype = { takeCell: function() { this._remainingCells -= 1; },

hasAllCells: function() { return this._remainingCells === 0; }};

Wednesday, May 16, 12

var CellTracker = function(totalCells) { this._remainingCells = totalCells;};

CellTracker.prototype = { takeCell: function() { this._remainingCells -= 1; },

hasAllCells: function() { return this._remainingCells === 0; }};

src/player.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

var DOMAlertRenderer = require('ui/dom_alert_renderer').DOMAlertRenderer;

describe('DOMAlertRenderer', function() { it('renders an alert when a player wins the game', function() { var renderer = new DOMAlertRenderer(); renderer.playerWonGame({player: 'X'});

expect(document.body.querySelector('.alert').innerText).toEqual("'X' Wins"); });});

Wednesday, May 16, 12

var DOMAlertRenderer = require('ui/dom_alert_renderer').DOMAlertRenderer;

describe('DOMAlertRenderer', function() { it('renders an alert when a player wins the game', function() { var renderer = new DOMAlertRenderer(); renderer.playerWonGame({player: 'X'});

expect(document.body.querySelector('.alert').innerText).toEqual("'X' Wins"); });});

spec/integration/dom_alert_renderer_spec.js

Wednesday, May 16, 12

Failure

Wednesday, May 16, 12

rake test:unit

Failure

Wednesday, May 16, 12

var DOMAlertRenderer = function() {};

DOMAlertRenderer.prototype = { playerWonGame: function(playerInformation) { var element = document.createElement('div'); element.className = 'alert'; element.innerText = ”’” + playerInformation.player + “‘ Wins”; document.body.appendChild(element); }};

Wednesday, May 16, 12

var DOMAlertRenderer = function() {};

DOMAlertRenderer.prototype = { playerWonGame: function(playerInformation) { var element = document.createElement('div'); element.className = 'alert'; element.innerText = ”’” + playerInformation.player + “‘ Wins”; document.body.appendChild(element); }};

ui/dom_alert_renderer.js

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:unit

Passed

Wednesday, May 16, 12

Passed

Wednesday, May 16, 12

rake test:acceptance

Passed

Wednesday, May 16, 12

Other Features

Wednesday, May 16, 12

AI Player?

Wednesday, May 16, 12

DOMBoardView

Main

TurnTracker

PlayerplayerOwnsNewCell

markCell

DOMAlert

RenderershowPlayerWonGame

receiveCell

Wednesday, May 16, 12

Remote Player?

Wednesday, May 16, 12

DOMBoardView

Main

TurnTracker

PlayerplayerOwnsNewCell

markCell

DOMAlert

RenderershowPlayerWonGame

receiveCell

Wednesday, May 16, 12

Questions?

Gregory Moeck@gregmoeck

Wednesday, May 16, 12

top related