extending your tdd cycle into javascriptfiles.meetup.com/310796/tdd_javascript.pdf · 1 wednesday,...
Post on 22-Sep-2020
6 Views
Preview:
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