From 92f2806e314d25e535f69668fc862a650990be3e Mon Sep 17 00:00:00 2001 From: "Jesse C. Fisher" Date: Sat, 20 Feb 2016 02:19:28 -0800 Subject: [PATCH] Add games to seats and implement tables --- Gemfile.lock | 160 +++++++++--------- Guardfile | 3 +- GuardfileServer | 1 + README.md | 8 + app/assets/javascripts/application.js | 2 +- .../components/flash_messages.js.jsx | 2 +- app/assets/javascripts/components/game.js.jsx | 21 ++- .../javascripts/components/player.js.jsx | 6 +- .../javascripts/components/player_list.js.jsx | 39 ++++- app/assets/javascripts/components/seat.js.jsx | 79 ++++++--- .../javascripts/components/seat_list.js.jsx | 35 ++++ .../javascripts/components/table.js.jsx | 31 +++- app/assets/javascripts/seat.js.coffee | 3 + app/assets/stylesheets/seat.css.scss | 3 + app/controllers/games_controller.rb | 12 +- app/controllers/seats_controller.rb | 85 ++++++++++ app/helpers/seat_helper.rb | 2 + app/models/game.rb | 38 ++++- app/models/seat.rb | 48 ++++++ app/models/table.rb | 25 ++- app/models/user.rb | 19 +++ app/views/games/show.html.haml | 2 +- app/views/games/show.json.jbuilder | 42 ++++- app/views/seats/show.json.jbuilder | 1 + app/views/seats/sit.json.jbuilder | 1 + app/views/seats/stand.json.jbuilder | 1 + app/views/tables/show.json.jbuilder | 12 ++ config/initializers/react_rails_hot_loader.rb | 2 +- config/routes.rb | 7 +- .../20160209120734_add_position_to_seats.rb | 5 + ...0212053521_add_user_soft_token_to_seats.rb | 5 + .../20160219012611_add_game_id_to_seats.rb | 5 + db/schema.rb | 5 +- spec/factories/seats.rb | 7 +- spec/factories/tables.rb | 1 - spec/factories/users.rb | 2 +- spec/features/play_now_spec.rb | 79 ++++++--- spec/features/plays_spec.rb | 15 +- spec/features/seats/seats_sit_spec.rb | 108 ++++++++++++ spec/features/tables/table_index_spec.rb | 2 +- spec/features/tables/table_join_spec.rb | 75 +++++--- spec/features/tables/table_show_spec.rb | 60 +++++++ spec/features/users/sign_in_spec.rb | 2 +- spec/features/visitors/game_join_spec.rb | 5 + spec/models/seat_spec.rb | 18 ++ spec/models/table_spec.rb | 4 + spec/support/helpers/game_helpers.rb | 8 +- 47 files changed, 905 insertions(+), 191 deletions(-) create mode 100644 app/assets/javascripts/components/seat_list.js.jsx create mode 100644 app/assets/javascripts/seat.js.coffee create mode 100644 app/assets/stylesheets/seat.css.scss create mode 100644 app/controllers/seats_controller.rb create mode 100644 app/helpers/seat_helper.rb create mode 100644 app/views/seats/show.json.jbuilder create mode 100644 app/views/seats/sit.json.jbuilder create mode 100644 app/views/seats/stand.json.jbuilder create mode 100644 db/migrate/20160209120734_add_position_to_seats.rb create mode 100644 db/migrate/20160212053521_add_user_soft_token_to_seats.rb create mode 100644 db/migrate/20160219012611_add_game_id_to_seats.rb create mode 100644 spec/features/seats/seats_sit_spec.rb create mode 100644 spec/features/tables/table_show_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 69c19dd9..6eeaf40c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -34,12 +34,10 @@ GEM minitest (~> 5.1) thread_safe (~> 0.1) tzinfo (~> 1.1) - addressable (2.3.8) + addressable (2.4.0) arel (5.0.1.20140414130214) - ast (2.1.0) - astrolabe (1.3.1) - parser (~> 2.2) - babel-source (5.8.26) + ast (2.2.0) + babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) execjs (~> 2.0) @@ -62,25 +60,26 @@ GEM capistrano (>= 3.0.0) capistrano-postgresql (4.2.1) capistrano (>= 3.0) - capistrano-rails (1.1.5) + capistrano-rails (1.1.6) capistrano (~> 3.1) capistrano-bundler (~> 1.1) - capistrano-rails-console (1.0.0) + capistrano-rails-console (1.0.2) capistrano (>= 3.1.0, < 4.0.0) - capistrano-rbenv (2.0.3) + capistrano-rbenv (2.0.4) capistrano (~> 3.1) sshkit (~> 1.3) capistrano3-puma (1.2.1) capistrano (~> 3.0) puma (>= 2.6) - capybara (2.5.0) + capybara (2.6.2) + addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) chartkick (1.4.1) - childprocess (0.5.6) + childprocess (0.5.9) ffi (~> 1.0, >= 1.0.11) cliver (0.3.2) coderay (1.1.0) @@ -90,38 +89,37 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.9.1.1) - colorize (0.7.7) + coffee-script-source (1.10.0) connection_pool (2.2.0) database_cleaner (1.5.1) debug_inspector (0.0.2) - devise (3.5.2) + devise (3.5.6) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) responders thread_safe (~> 0.1) warden (~> 1.2.3) - devise_invitable (1.5.3) + devise_invitable (1.5.5) actionmailer (>= 3.2.6, < 5) devise (>= 3.2.0) diff-lcs (1.2.5) - dotenv (2.0.2) - dotenv-rails (2.0.2) - dotenv (= 2.0.2) - railties (~> 4.0) + dotenv (2.1.0) + dotenv-rails (2.1.0) + dotenv (= 2.1.0) + railties (>= 4.0, < 5.1) em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) erubis (2.7.0) - eventmachine (1.0.8) + eventmachine (1.0.9.1) execjs (2.6.0) factory_girl (4.5.0) activesupport (>= 3.0.0) - factory_girl_rails (4.5.0) + factory_girl_rails (4.6.0) factory_girl (~> 4.5.0) railties (>= 3.0.0) - faker (1.5.0) + faker (1.6.1) i18n (~> 0.5) ffi (1.9.10) formatador (0.2.5) @@ -142,7 +140,7 @@ GEM guard-ctags-bundler (1.4.0) guard (>= 2.0) guard-compat (>= 0.1.0) - guard-livereload (2.5.1) + guard-livereload (2.5.2) em-websocket (~> 0.5) guard (~> 2.8) guard-compat (~> 1.0) @@ -177,8 +175,8 @@ GEM hub (1.12.4) i18n (0.7.0) interception (0.5) - jbuilder (2.3.2) - activesupport (>= 3.0.0, < 5) + jbuilder (2.4.1) + activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) jquery-rails (3.1.4) railties (>= 3.0, < 5.0) @@ -189,37 +187,37 @@ GEM activesupport (>= 3.0.0) launchy (2.4.3) addressable (~> 2.3) - listen (3.0.3) + listen (3.0.6) rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9) - lumberjack (1.0.9) + rb-inotify (>= 0.9.7) + lumberjack (1.0.10) mail (2.6.3) mime-types (>= 1.16, < 3) method_source (0.8.2) - mime-types (2.6.2) - mini_portile (0.6.2) - minitest (5.8.1) + mime-types (2.99) + mini_portile2 (2.0.0) + minitest (5.8.4) multi_json (1.11.2) - nenv (0.2.0) + nenv (0.3.0) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (3.0.1) - newrelic_rpm (3.14.1.311) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) + net-ssh (3.0.2) + newrelic_rpm (3.14.3.313) + nokogiri (1.6.7.2) + mini_portile2 (~> 2.0.0.rc2) notiffany (0.0.8) nenv (~> 0.1) shellany (~> 0.0) orm_adapter (0.5.0) - parser (2.2.3.0) - ast (>= 1.1, < 3.0) - pg (0.18.3) - poltergeist (1.7.0) + parser (2.3.0.5) + ast (~> 2.2) + pg (0.18.4) + poltergeist (1.9.0) capybara (~> 2.1) cliver (~> 0.3.1) multi_json (~> 1.0) websocket-driver (>= 0.2.0) - polyamorous (1.2.0) + polyamorous (1.3.0) activerecord (>= 3.0) powerpack (0.1.1) pry (0.10.3) @@ -231,8 +229,8 @@ GEM pry-rescue (1.4.2) interception (>= 0.5) pry - puma (2.14.0) - pundit (1.0.1) + puma (2.16.0) + pundit (1.1.0) activesupport (>= 3.0.0) quiet_assets (1.1.0) railties (>= 3.1, < 5.0) @@ -251,14 +249,14 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.1.8) sprockets-rails (~> 2.0) - rails_layout (1.0.26) + rails_layout (1.0.29) railties (4.1.8) actionpack (= 4.1.8) activesupport (= 4.1.8) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.0.0) - rake (10.4.2) + rainbow (2.1.0) + rake (10.5.0) ransack (1.7.0) actionpack (>= 3.0) activerecord (>= 3.0) @@ -267,53 +265,53 @@ GEM polyamorous (~> 1.2) rb-fchange (0.0.6) ffi - rb-fsevent (0.9.6) - rb-inotify (0.9.5) + rb-fsevent (0.9.7) + rb-inotify (0.9.7) ffi (>= 0.5.0) - rdoc (4.2.0) + rdoc (4.2.2) json (~> 1.4) - react-rails (1.3.2) + react-rails (1.6.0) babel-transpiler (>= 0.7.0) coffee-script-source (~> 1.8) connection_pool execjs rails (>= 3.2) tilt - react-rails-hot-loader (0.4.0) + react-rails-hot-loader (0.5.0) em-websocket rails react-rails responders (1.1.2) railties (>= 3.2, < 4.2) - rspec (3.3.0) - rspec-core (~> 3.3.0) - rspec-expectations (~> 3.3.0) - rspec-mocks (~> 3.3.0) - rspec-core (3.3.2) - rspec-support (~> 3.3.0) - rspec-expectations (3.3.1) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-core (3.4.2) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-mocks (3.3.2) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.3.0) - rspec-rails (3.3.3) + rspec-support (~> 3.4.0) + rspec-rails (3.4.2) actionpack (>= 3.0, < 4.3) activesupport (>= 3.0, < 4.3) railties (>= 3.0, < 4.3) - rspec-core (~> 3.3.0) - rspec-expectations (~> 3.3.0) - rspec-mocks (~> 3.3.0) - rspec-support (~> 3.3.0) - rspec-support (3.3.0) - rubocop (0.34.2) - astrolabe (~> 1.3) - parser (>= 2.2.2.5, < 3.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-support (~> 3.4.0) + rspec-support (3.4.1) + rubocop (0.37.2) + parser (>= 2.3.0.4, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) - ruby-progressbar (~> 1.4) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 0.3) ruby-progressbar (1.7.5) - ruby_parser (3.7.1) + ruby_parser (3.7.3) sexp_processor (~> 4.1) rubyzip (1.1.7) sass (3.2.19) @@ -325,18 +323,18 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - selenium-webdriver (2.48.1) + selenium-webdriver (2.52.0) childprocess (~> 0.5) multi_json (~> 1.0) rubyzip (~> 1.0) websocket (~> 1.0) - sexp_processor (4.6.0) + sexp_processor (4.6.1) shellany (0.0.1) - simple_form (3.2.0) - actionpack (~> 4.0) - activemodel (~> 4.0) + simple_form (3.2.1) + actionpack (> 4, < 5.1) + activemodel (> 4, < 5.1) slop (3.6.0) - spring (1.4.0) + spring (1.6.3) spring-commands-rspec (1.0.4) spring (>= 0.9.1) sprockets (2.12.4) @@ -348,8 +346,7 @@ GEM actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) - sshkit (1.7.1) - colorize (>= 0.7.0) + sshkit (1.8.1) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) sysexits (1.2.0) @@ -363,6 +360,7 @@ GEM uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) + unicode-display_width (0.3.1) upmin-admin (0.1.3) chartkick haml (>= 3.0.0) @@ -371,10 +369,10 @@ GEM rails ransack sass-rails - warden (1.2.3) + warden (1.2.6) rack (>= 1.0) websocket (1.2.2) - websocket-driver (0.6.2) + websocket-driver (0.6.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) xpath (2.0.0) diff --git a/Guardfile b/Guardfile index 8e3f9b1d..8859fbd4 100644 --- a/Guardfile +++ b/Guardfile @@ -50,9 +50,10 @@ end # * zeus: 'zeus rspec' (requires the server to be started separately) # * 'just' rspec: 'rspec' -guard :rspec, cmd: "bundle exec spring rspec", all_after_pass: false, failed_mode: :focus do # +guard :rspec, cmd: "bundle exec spring rspec", all_after_pass: false do#, failed_mode: :focus require "guard/rspec/dsl" dsl = Guard::RSpec::Dsl.new(self) + notification :off # Feel free to open issues for suggestions and improvements diff --git a/GuardfileServer b/GuardfileServer index 23d960ea..7b961d35 100644 --- a/GuardfileServer +++ b/GuardfileServer @@ -38,6 +38,7 @@ end guard 'rails', host: '0.0.0.0' do watch('Gemfile.lock') watch(%r{^(config|lib)/.*}) + notification :off end diff --git a/README.md b/README.md index d363ea81..45826256 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Thirteen Tien Len TODO ---- +- Refactor seat.jsx buttons in to a
. + - 1st 2nd 3rd place messages. End game message. - Tables to contain the games @@ -36,6 +38,12 @@ first play - Review attributions and license compliance +- TODO: Refactor guardfiles down to one file using `groups` + https://github.com/guard/guard/wiki/Guardfile-examples + +- TODO: Move game logic out of game model / controller to module in + /lib/game_logic.rb e.g. https://github.com/adamof/rails-poker/tree/master/poker + ## Proposed Schema Table diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 9184969f..8ba77ec0 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -16,5 +16,5 @@ //= require react //= require react_ujs //= require components -//= require react-rails-hot-loader +// require react-rails-hot-loader //= require_tree . diff --git a/app/assets/javascripts/components/flash_messages.js.jsx b/app/assets/javascripts/components/flash_messages.js.jsx index dae9e41a..2251aa29 100644 --- a/app/assets/javascripts/components/flash_messages.js.jsx +++ b/app/assets/javascripts/components/flash_messages.js.jsx @@ -57,7 +57,7 @@ function handleFlashMessagesHeader(node, xhr) { $(document).ready(function() { var dummy = new Array(); - var flashDiv = React.render(, $('#flash_messages')[0]); + var flashDiv = ReactDOM.render(, $('#flash_messages')[0]); $(document).ajaxComplete(function(event, xhr, settings) { handleFlashMessagesHeader(flashDiv, xhr); diff --git a/app/assets/javascripts/components/game.js.jsx b/app/assets/javascripts/components/game.js.jsx index cc89e1e5..80ca117a 100644 --- a/app/assets/javascripts/components/game.js.jsx +++ b/app/assets/javascripts/components/game.js.jsx @@ -5,10 +5,14 @@ var Game = React.createClass({ title: React.PropTypes.string, players: React.PropTypes.array, id: React.PropTypes.number, - url: React.PropTypes.string, play_to_beat_string: React.PropTypes.string }, + url: function() { + var url = "/games/" + this.props.id; + return url; + }, + getInitialState: function() { return { data: null, @@ -22,7 +26,7 @@ var Game = React.createClass({ }, loadGameFromServer: function() { - $.getJSON(this.props.url, function(result) { + $.getJSON(this.url(), function(result) { if (this.isMounted()) { this.setState({ data: result }); } @@ -35,8 +39,9 @@ var Game = React.createClass({ }, handlePlayHandSubmit: function(card_ids) { + console.log("Hello from handlePlayHandSubmit()"); $.ajax({ - url: this.props.url + '/play_hand', + url: this.url() + '/play_hand', type: 'PATCH', dataType: 'json', data: card_ids, @@ -45,14 +50,14 @@ var Game = React.createClass({ this.loadGameFromServer(); }.bind(this), error: function(xhr, status, err) { - console.error(this.props.url, status, err.toString()); + console.error(this.url(), status, err.toString()); }.bind(this) }); }, handleStart: function() { $.ajax({ - url: this.props.url + '/start', + url: this.url() + '/start', type: 'POST', dataType: 'json', complete: function (jqXHR, textStatus) { @@ -69,7 +74,7 @@ var Game = React.createClass({ handleJoin: function() { $.ajax({ - url: this.props.url + '/join', + url: this.url() + '/join', type: 'POST', dataType: 'json', complete: function (jqXHR, textStatus) { @@ -98,7 +103,7 @@ var Game = React.createClass({ :
{/* TODO: If not joinable, show why. i.e. game full*/ } - {this.state.data.is_joinable ? : null } + {/* TODO: DEPRECATED use seat sit instead this.state.data.is_joinable ? : null */} {/* TODO: If not startable, show why. i.e. need more players */}
{this.state.data.is_started ? null : } @@ -108,6 +113,8 @@ var Game = React.createClass({ {this.state.data.winner_player_id ? "Winner: " + this.state.data.winner_player_id : null} diff --git a/app/assets/javascripts/components/player.js.jsx b/app/assets/javascripts/components/player.js.jsx index b466f9a9..507f7ec1 100644 --- a/app/assets/javascripts/components/player.js.jsx +++ b/app/assets/javascripts/components/player.js.jsx @@ -8,10 +8,10 @@ var Player = React.createClass({ return (
{this.props.email || "Guest" + this.props.soft_token.substr(0,6)}
- {this.props.game.is_started - ? + {this.props.game.is_started ?
Inventory count: {this.props.inventory_count}
- : null } + : null } + {/* Show player controls if player is owned by current_user */} {(this.props.soft_token == this.props.game.current_user_soft_token) && (this.props.game.is_started == true) diff --git a/app/assets/javascripts/components/player_list.js.jsx b/app/assets/javascripts/components/player_list.js.jsx index 5f6de9c8..2e110f27 100644 --- a/app/assets/javascripts/components/player_list.js.jsx +++ b/app/assets/javascripts/components/player_list.js.jsx @@ -4,6 +4,7 @@ var PlayerList = React.createClass({ game_id: React.PropTypes.number }, + /* TODO: DEPRECATED playerNodes */ playerNodes: function() { var playerList = this; var playerNodes = this.props.game.players.map(function (player) { @@ -23,15 +24,45 @@ var PlayerList = React.createClass({ return (
- {this.playerNodes()[0]} +
+ {console.log("Hello from player_list.jsx")} + {console.log(this.props.onPlayHandSubmit)} + +
- {this.playerNodes()[1]} +
+ +
Hand to beat: {this.props.game.play_to_beat_string}
- {this.playerNodes()[2]} +
+ +
- {this.playerNodes()[3]} +
+ +
); diff --git a/app/assets/javascripts/components/seat.js.jsx b/app/assets/javascripts/components/seat.js.jsx index 679ada86..155762e2 100644 --- a/app/assets/javascripts/components/seat.js.jsx +++ b/app/assets/javascripts/components/seat.js.jsx @@ -1,29 +1,68 @@ var Seat = React.createClass({ - onClick: function(e) { - // TODO: Sit at table - $.ajax({ - url: window.location.pathname + '/sit', - type: 'POST', - dataType: 'json', - complete: function (jqXHR, textStatus) { - // callback - }, - success: function (data, textStatus, jqXHR) { - // success callback - }, - error: function (jqXHR, textStatus, errorThrown) { - // error callback - } - }); + isOccupied: function() { + //TODO: create isOccupied logic + return false; + }, + + url: function() { + var url = "/seats/" + this.props.id + ".json" + return url; + }, + + sitClick: function(e) { + this.props.onSeatSubmit(this.props.id, 'sit'); + }, + + standClick: function(e) { + this.props.onSeatSubmit(this.props.id, 'stand'); + }, + + isAvailable: function() { + return this.props.user_display_name == null + }, + + sitButtonStyle: function() { + var style = {}; + if(null != this.props.user_id == this.props.current_user.id) + style = { display: 'none'}; + if(this.props.user_soft_token == this.props.current_user.soft_token) + style = { display: 'none'}; + return style; + }, + + standButtonStyle: function() { + var style = { display: 'none'}; + if(null != this.props.user_id == this.props.current_user.id) + style = {}; + if(this.props.user_soft_token == this.props.current_user.soft_token) + style = {}; + return style; }, render: function() { return ( - +
+
+ + +
+ {this.props.player ? + + : "Empty" } +
); } - }); diff --git a/app/assets/javascripts/components/seat_list.js.jsx b/app/assets/javascripts/components/seat_list.js.jsx new file mode 100644 index 00000000..7f461201 --- /dev/null +++ b/app/assets/javascripts/components/seat_list.js.jsx @@ -0,0 +1,35 @@ +var SeatList = React.createClass({ + propTypes: { + seats: React.PropTypes.array + }, + + seatNodes: function() { + var onSeatSubmit = this.props.onSeatSubmit; + var current_user = this.props.current_user; + var seatNodes = this.props.seats.map(function(seat){ + return ( + + ) + }) + return seatNodes; + }, + + render: function() { + return ( +
+ {this.seatNodes()[0]} + {this.seatNodes()[1]} + {this.seatNodes()[2]} + {this.seatNodes()[3]} +
+ ); + } + +}); diff --git a/app/assets/javascripts/components/table.js.jsx b/app/assets/javascripts/components/table.js.jsx index b847ea6d..5a345ae0 100644 --- a/app/assets/javascripts/components/table.js.jsx +++ b/app/assets/javascripts/components/table.js.jsx @@ -14,7 +14,7 @@ var Table = React.createClass({ }; }, - componentDidMount: function() { + loadTableFromServer: function() { $.getJSON(this.props.url, function(result) { if (this.isMounted()) { this.setState({ data: result }); @@ -22,6 +22,29 @@ var Table = React.createClass({ }.bind(this)); }, + + componentDidMount: function() { + this.loadTableFromServer(); + }, + + handleSeatSubmit: function(seat_id, action) { + console.log("handleSeatSubmit"); + $.ajax({ + url: window.location.pathname + '/seats/' + seat_id + '/' + action, + type: 'PATCH', + dataType: 'json', + success: function (data, textStatus, jqXHR) { + // success callback + console.log(data.message); + this.loadTableFromServer(); + }.bind(this), + error: function (jqXHR, textStatus, errorThrown) { + // error callback + console.log(data.message); + } + }); + }, + render: function() { if (!this.state.data) { return
Loading...
; @@ -29,10 +52,8 @@ var Table = React.createClass({ return (

{this.state.data.title}

- - - - + {/* TODO: DEPRECATED seats are now rendered within games */} +
); } diff --git a/app/assets/javascripts/seat.js.coffee b/app/assets/javascripts/seat.js.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/seat.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/seat.css.scss b/app/assets/stylesheets/seat.css.scss new file mode 100644 index 00000000..19efa1d9 --- /dev/null +++ b/app/assets/stylesheets/seat.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Seat controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb index 81a94cdd..69fccdef 100644 --- a/app/controllers/games_controller.rb +++ b/app/controllers/games_controller.rb @@ -4,11 +4,18 @@ class GamesController < ApplicationController respond_to :html, :json def index - @games = Game.all.order :id + if params[:table_id] + @table = Table.find(params[:table_id]) + @games = @table.games + else + @games = Game.all.order :id + end respond_with(@games) end def show + #TODO: Implement standalone games, without a table? + #redirect_to @game.table respond_with(@game) end @@ -98,7 +105,8 @@ class GamesController < ApplicationController def play_now # First joinable game or create new game @game = Game.play_now(current_user) - @game.add_player_from_user current_user + @seats = @game.seats.order(:position).map {|s| s.occupied? ? nil : s} + @seats.compact.last.sit(current_user) redirect_to @game end diff --git a/app/controllers/seats_controller.rb b/app/controllers/seats_controller.rb new file mode 100644 index 00000000..9551ef1b --- /dev/null +++ b/app/controllers/seats_controller.rb @@ -0,0 +1,85 @@ +class SeatsController < ApplicationController + before_action :set_seat, only: [:show, :edit, :update, :destroy, :sit, :stand] + + respond_to :html, :json + + # GET /seats + # GET /seats.json + def index + @seats = Seat.all + end + + # GET /seats/1 + # GET /seats/1.json + def show + end + + # GET /seats/new + def new + @seat = Seat.new + end + + # GET /seats/1/edit + def edit + end + + # POST /seats + # POST /seats.json + def create + @seat = Seat.new(seat_params) + + respond_to do |format| + if @seat.save + format.html { redirect_to @seat, notice: 'Seat was successfully created.' } + format.json { render :show, status: :created, location: @seat } + else + format.html { render :new } + format.json { render json: @seat.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /seats/1 + # PATCH/PUT /seats/1.json + def update + respond_to do |format| + if @seat.update(seat_params) + format.html { redirect_to @seat, notice: 'Seat was successfully updated.' } + format.json { render :show, status: :ok, location: @seat } + else + format.html { render :edit } + format.json { render json: @seat.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /seats/1 + # DELETE /seats/1.json + def destroy + @seat.destroy + respond_to do |format| + format.html { redirect_to seats_url, notice: 'Seat was successfully destroyed.' } + format.json { head :no_content } + end + end + + # POST /seats/1 + def sit + @message = @seat.sit current_user + end + + def stand + @message = @seat.stand current_user + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_seat + @seat = Seat.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def seat_params + params.require(:seat).permit(:position, :table_id, :user_id) + end +end diff --git a/app/helpers/seat_helper.rb b/app/helpers/seat_helper.rb new file mode 100644 index 00000000..7827ed90 --- /dev/null +++ b/app/helpers/seat_helper.rb @@ -0,0 +1,2 @@ +module SeatHelper +end diff --git a/app/models/game.rb b/app/models/game.rb index 64eb77fa..13c0568b 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -1,6 +1,7 @@ # The game class class Game < ActiveRecord::Base belongs_to :table + has_many :seats has_many :players, dependent: :destroy has_many :users, through: :players has_many :player_cards, through: :players @@ -8,10 +9,12 @@ class Game < ActiveRecord::Base has_one :controlling_player belongs_to :play_to_beat accepts_nested_attributes_for :players - validates :title, presence: true + #validates :title, presence: true + after_create :add_table + # Returns the newest joinable game or creates a new game if none are found scope :play_now, lambda { |user| - where(status:nil).joins("LEFT OUTER JOIN players ON players.game_id = games.id").group("games.id").having("count(players) < 4").first || Game.create(title: "Game " + user.soft_token[0..4]) + where(status:nil).joins("LEFT OUTER JOIN players ON players.game_id = games.id").group("games.id").having("count(players) < 4").last || Game.create(title: "Game " + user.soft_token[0..4]) } def start @@ -72,14 +75,37 @@ class Game < ActiveRecord::Base new_player = self.players.create(user: user, soft_token: user.soft_token) end + def remove_player_from_user(user) + return false unless self.has_user? user + + if self.players.exists?(user_id: user.id) + @player = self.players.find_by(user_id: user.id) + elsif self.players.exists?(soft_token: user.soft_token) + @player = self.players.find_by(soft_token: user.soft_token) + end + + if @player + @player.destroy + end + + end + def can_accomodate(user) !(already_has?(user) || full?) end + #TODO: Which has_user? method is better? Probably has_user?. def already_has?(user) self.players.pluck(:user_id, :soft_token).flatten.compact.to_set.intersect? ([user.id, user.soft_token].to_set) end + def has_user? user + return true if self.players.exists?(user_id: user.id) + return true if self.players.exists?(soft_token: user.soft_token) + return false + end + + def full? players.count >= 4 end @@ -130,4 +156,12 @@ class Game < ActiveRecord::Base @inventory_counts.include? 0 end +private + + def add_table + self.table ||= Table.create current_game_id: self.id + self.seats = self.table.seats + self.save + end + end diff --git a/app/models/seat.rb b/app/models/seat.rb index dc8427c7..06b20c4b 100644 --- a/app/models/seat.rb +++ b/app/models/seat.rb @@ -1,4 +1,52 @@ class Seat < ActiveRecord::Base belongs_to :table + belongs_to :game belongs_to :user + + def sit(current_user) + if self.occupied? + return "Error. Seat is occupied." + else + # Stand up from any other seats at this table + current_user.stand_from(self.table) + + self.user_id = current_user.id + self.user_soft_token = current_user.soft_token + self.save + + self.table.current_game.add_player_from_user(current_user) + return "Successfully sat." + end + end + + def stand(current_user) + unless self.occupied_by?(current_user) + return "Error. Seat not occupied by you." + else + self.user_id = nil + self.user_soft_token = nil + self.save + + self.table.current_game.remove_player_from_user(current_user) + return "Successfully stood up." + end + end + + def occupied? + self.user_id.present? || self.user_soft_token.present? + end + + def occupied_by? user + if user.id.present? + self.user_id == user.id + end + if user.soft_token.present? + self.user_soft_token == user.soft_token + end + end + + def user_display_name + return nil unless self.occupied? + self.user.try(:email) || "Guest" + self.user_soft_token.to_s[0..4] + end end diff --git a/app/models/table.rb b/app/models/table.rb index 9f235438..d85f3da1 100644 --- a/app/models/table.rb +++ b/app/models/table.rb @@ -3,5 +3,28 @@ class Table < ActiveRecord::Base has_many :seats has_many :users, through: :seats has_many :games - has_one :current_game, :class_name => "Game", :primary_key => "current_game_id" + #TODO: Deprecated current_game_id from database. + # Instead calculate with self.current_game + # + #has_one :current_game, :class_name => "Game", :primary_key => "current_game_id" + + #TODO: Should these be part of Game initialize instead of callback? + after_create :add_seats + after_create :add_game + + def current_game + self.games.last + end + + private + + def add_seats + 1.upto(4) do |i| + self.seats.create(position: i) + end + end + + def add_game + self.games.create if self.current_game_id.blank? + end end diff --git a/app/models/user.rb b/app/models/user.rb index c8b49520..285f3302 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,6 +16,25 @@ class User < ActiveRecord::Base !soft_user? end + def display_name + self.email || "Guest#{self.soft_token[0..5]}" + end + + def stand_from(table) + @seats = [] + if self.id + @seats << table.seats.where(user_id: self.id) + end + if self.soft_token + @seats << table.seats.where(user_soft_token: self.soft_token) + end + @seats.flatten.each do |seat| + seat.user_id = nil + seat.user_soft_token = nil + seat.save + end + end + # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :invitable, :database_authenticatable, :registerable, # :confirmable, diff --git a/app/views/games/show.html.haml b/app/views/games/show.html.haml index 08a511fb..6a2756a2 100644 --- a/app/views/games/show.html.haml +++ b/app/views/games/show.html.haml @@ -1,4 +1,4 @@ -#game-component-container-container= react_component 'Game', props = {url: "#{game_path @game}"}, html_options = {id: "game-component-container"} +#game-component-container-container= react_component 'Game', props = {id: @game.id, current_user: current_user}, html_options = {id: "game-component-container"} -# TODO: Remove deprecated commented out code -#%p diff --git a/app/views/games/show.json.jbuilder b/app/views/games/show.json.jbuilder index 9a3d627d..c136e61a 100644 --- a/app/views/games/show.json.jbuilder +++ b/app/views/games/show.json.jbuilder @@ -13,14 +13,40 @@ json.is_startable @game.startable? json.current_user_id current_user.try(:id) json.current_user_soft_token current_user.try(:soft_token) -json.players @game.players do |player| - json.id player.id - json.email player.user.try(:email) - json.inventory_count player.inventory.count - json.user_id player.user.try(:id) - json.soft_token player.soft_token +#json.players @game.players do |player| +# json.id player.id +# json.email player.user.try(:email) +# json.inventory_count player.inventory.count +# json.user_id player.user.try(:id) +# json.soft_token player.soft_token +# +# if player.soft_token == current_user.soft_token +# json.inventory player.inventory +# end +#end - if player.soft_token == current_user.soft_token - json.inventory player.inventory +json.seats @game.seats.order(:position) do |seat| + json.id seat.id + json.table_id seat.table_id + json.position seat.position + json.user_id seat.user_id + json.user_soft_token seat.user_soft_token + json.user_display_name seat.user_display_name + + json.set! :player do + @player = nil + if !seat.user_id.blank? + @player = @game.players.find_by(user_id: seat.user_id) + elsif !seat.user_soft_token.blank? + @player = @game.players.find_by(soft_token: seat.user_soft_token) + end + if @player + json.id @player.id + json.email @player.user.try(:email) + json.inventory_count @player.inventory.count + json.user_id @player.user_id + json.soft_token @player.soft_token + json.inventory @player.inventory if @player.soft_token == current_user.soft_token + end end end diff --git a/app/views/seats/show.json.jbuilder b/app/views/seats/show.json.jbuilder new file mode 100644 index 00000000..cfce197a --- /dev/null +++ b/app/views/seats/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @seat, :id, :table_id, :user_id, :user_soft_token diff --git a/app/views/seats/sit.json.jbuilder b/app/views/seats/sit.json.jbuilder new file mode 100644 index 00000000..747317ea --- /dev/null +++ b/app/views/seats/sit.json.jbuilder @@ -0,0 +1 @@ +json.message @message diff --git a/app/views/seats/stand.json.jbuilder b/app/views/seats/stand.json.jbuilder new file mode 100644 index 00000000..747317ea --- /dev/null +++ b/app/views/seats/stand.json.jbuilder @@ -0,0 +1 @@ +json.message @message diff --git a/app/views/tables/show.json.jbuilder b/app/views/tables/show.json.jbuilder index ca42e255..dff42260 100644 --- a/app/views/tables/show.json.jbuilder +++ b/app/views/tables/show.json.jbuilder @@ -1 +1,13 @@ json.extract! @table, :id, :title, :created_at, :updated_at + +json.current_game_id @table.current_game.id +json.current_user current_user + +json.seats @table.seats.order(:position) do |seat| + json.id seat.id + json.table_id seat.table_id + json.position seat.position + json.user_id seat.user_id + json.user_soft_token seat.user_soft_token + json.user_display_name seat.user_display_name +end diff --git a/config/initializers/react_rails_hot_loader.rb b/config/initializers/react_rails_hot_loader.rb index 60dda6ae..216c5752 100644 --- a/config/initializers/react_rails_hot_loader.rb +++ b/config/initializers/react_rails_hot_loader.rb @@ -1,3 +1,3 @@ if Rails.env.development? - React::Rails::HotLoader.start() + #React::Rails::HotLoader.start() end diff --git a/config/routes.rb b/config/routes.rb index f19e42a1..969cfff6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,12 @@ Rails.application.routes.draw do resources :players resources :tables do - resources :seats + resources :seats do + member do + patch 'sit' + patch 'stand' + end + end resources :games end diff --git a/db/migrate/20160209120734_add_position_to_seats.rb b/db/migrate/20160209120734_add_position_to_seats.rb new file mode 100644 index 00000000..6b1fbca5 --- /dev/null +++ b/db/migrate/20160209120734_add_position_to_seats.rb @@ -0,0 +1,5 @@ +class AddPositionToSeats < ActiveRecord::Migration + def change + add_column :seats, :position, :integer + end +end diff --git a/db/migrate/20160212053521_add_user_soft_token_to_seats.rb b/db/migrate/20160212053521_add_user_soft_token_to_seats.rb new file mode 100644 index 00000000..599ca532 --- /dev/null +++ b/db/migrate/20160212053521_add_user_soft_token_to_seats.rb @@ -0,0 +1,5 @@ +class AddUserSoftTokenToSeats < ActiveRecord::Migration + def change + add_column :seats, :user_soft_token, :string + end +end diff --git a/db/migrate/20160219012611_add_game_id_to_seats.rb b/db/migrate/20160219012611_add_game_id_to_seats.rb new file mode 100644 index 00000000..df0590b7 --- /dev/null +++ b/db/migrate/20160219012611_add_game_id_to_seats.rb @@ -0,0 +1,5 @@ +class AddGameIdToSeats < ActiveRecord::Migration + def change + add_column :seats, :game_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index d9c46ed8..d77beb43 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151231035240) do +ActiveRecord::Schema.define(version: 20160219012611) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -76,6 +76,9 @@ ActiveRecord::Schema.define(version: 20151231035240) do t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" + t.integer "position" + t.string "user_soft_token" + t.integer "game_id" end add_index "seats", ["table_id"], name: "index_seats_on_table_id", using: :btree diff --git a/spec/factories/seats.rb b/spec/factories/seats.rb index 8dace51f..6c70e6d8 100644 --- a/spec/factories/seats.rb +++ b/spec/factories/seats.rb @@ -1,7 +1,10 @@ FactoryGirl.define do factory :seat do table { FactoryGirl.create(:table) } - user { FactoryGirl.create(:user) } - end + trait :occupied do + user { FactoryGirl.create(:user) } + user_soft_token { user.soft_token } + end + end end diff --git a/spec/factories/tables.rb b/spec/factories/tables.rb index c627998d..8eb00404 100644 --- a/spec/factories/tables.rb +++ b/spec/factories/tables.rb @@ -1,7 +1,6 @@ FactoryGirl.define do factory :table do sequence(:title) { |n| "Test Table #{n}" } - current_game { FactoryGirl.create(:game) } end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 9f37412a..8ae27261 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -3,7 +3,7 @@ FactoryGirl.define do confirmed_at Time.now name 'Test User' sequence(:email) { |n| "test#{n}@example.com" } - password 'please123' + password 'password' sequence(:soft_token) { |n| "soft_token#{n}" } trait :admin do diff --git a/spec/features/play_now_spec.rb b/spec/features/play_now_spec.rb index 71e9fc6a..80d17c0a 100644 --- a/spec/features/play_now_spec.rb +++ b/spec/features/play_now_spec.rb @@ -2,31 +2,68 @@ # As a visitor # I want to quickly join a game # So I can play -feature 'Play now', :devise do - before { @game = FactoryGirl.create :game } +feature 'Play now', :devise, js: true do # Scenario: Visitor can play now # Given I am not signed in # When I click Play Now # Then I join a game - scenario 'visitor can join a game', js: true do - visit root_path - click_link 'Play Now' - expect(page).to have_content "Test Game" - expect(current_path).to eq game_path(@game) - @player = @game.players.last - @playerComponent = page.find("#player-1") - expect(@playerComponent.text).to have_content("Guest#{@player.soft_token[0..5]}") + describe 'visitor can Play Now' do + context 'there are no joinable games' do + before do + expect(Game.all.count).to eq 0 + visit root_path + click_link 'Play Now' + end + it 'creates and joins a new game' do + #TODO: Fix race condition + expect(page).to have_content "Hand to beat" + #Game title derived from visitor soft_token + expect(page).to have_content "#{Game.last.players.first.soft_token[0..4]}" + #Game count increased by 1 + expect(Game.all.count).to eq 1 + end + end - # Add another player - @game.add_player_from_user FactoryGirl.create :user - #TODO: Remove race condition - visit current_path - click_button 'Start' - #TODO: Remove race condition - visit current_path - visit current_path - visit current_path - visit current_path - expect(page).to have_content 'Inventory' + context 'visitor joins an existing game' do + before do + FactoryGirl.create :game + @game = Game.last + visit root_path + expect(Game.all.count).to eq 1 + click_link 'Play Now' + end + + it 'shows and starts the game' do + #TODO: Deprecated it shows the game title + #expect(page).to have_content "Test Game" + expect(page).to have_content "Hand to beat" + #it 'is located at the correct url' do + expect(current_path).to eq game_path(@game) + + #it 'shows the visitor as a player' do + @player = @game.players.last + @playerComponent = page.find("#player-1") + #TODO: have_content @player.display_name + expect(@playerComponent.text).to have_content("Guest#{@player.soft_token[0..5]}") + + #it 'shows other players' do + # Add another player + @user2 = FactoryGirl.create :user + @seats = @game.table.seats.map { |s| s.occupied? ? nil : s} + @seats.compact.first.sit(@user2) + #TODO: Remove race condition + visit current_path + expect(page).to have_content @user2.display_name + #it 'the game is startable' do + click_button 'Start' + #TODO: Remove race condition + visit current_path + visit current_path + visit current_path + visit current_path + expect(page).to have_content 'Inventory' + expect(Game.all.count).to eq 1 + end + end end end diff --git a/spec/features/plays_spec.rb b/spec/features/plays_spec.rb index cd711526..846600d2 100644 --- a/spec/features/plays_spec.rb +++ b/spec/features/plays_spec.rb @@ -7,7 +7,7 @@ feature 'Play a hand', type: :feature, js: true do @game = setup_game @game.reload @player = @game.controlling_player - signin(@player.user.email,'please123') + signin(@player.user.email,'password') #login_as(@player.user, scope: :user) visit game_path @game end @@ -228,4 +228,17 @@ feature 'Play a hand', type: :feature, js: true do expect(page).to have_content 'Hand to beat: None' end + # Scenario: Player wins the game + # Given I am the active player + # And I play my last card(s) + # And I am the first player to do so + # Then I see a 1st place winner message + scenario 'first place winner' do + # play last card + pending "TBD" + expect(@player.inventory.count).to eq 0 + expect(page).to have_content "Winner: #{@player.display_name}" + end + + end diff --git a/spec/features/seats/seats_sit_spec.rb b/spec/features/seats/seats_sit_spec.rb new file mode 100644 index 00000000..ceca71cc --- /dev/null +++ b/spec/features/seats/seats_sit_spec.rb @@ -0,0 +1,108 @@ +# Feature: Seats +# As a visitor +# I want to sit in a seat +# So I can be at a table and play a game +feature 'Seats', :devise, js: true do + context "As a visitor" do + before(:each) do + @table = FactoryGirl.create :table + visit table_path @table + end + + it 'visitor can sit in an empty seat' do + click_button 'Sit', match: :first + expect(page).to have_content "Guest" + end + + it 'sit button is disabled for unavailable seats' do + @button = page.find_button('Sit', match: :first) + @button.click + expect(page).to have_content "Guest" + expect(@button.disabled?).to eq true + end + + it 'has a stand button if current user is seated' do + @button = page.find_button('Sit', match: :first) + @button.click + expect(page).to have_content "Guest" + @buttons = page.find_all('button', text: 'Stand') + expect(@buttons.length).to eq 1 + end + + scenario 'table seats match up with game seats' do + click_button 'Sit', match: :first + expect(page.find(:css, ".game-seat.position-2")).to have_content "Guest" + end + + #TODO: User can only occupy one seat per table + scenario 'visitor changes seats' do + click_button 'Sit', match: :first + expect(page).to have_content "Guest" + #TODO: Fix race condition + sleep 3 + click_button 'Sit', match: :first + expect(page).to have_content "Guest" + expect(page.text.scan('Guest').count).to eq 2 + end + + scenario 'visitor can join the current game' do + click_button 'Sit', match: :first + #TODO: Fix race condition. Trigger game react component reload when player joins a table + sleep 4 + expect(page).to have_content "Guest" + expect(page.text.scan('Guest').count).to eq 2 + end + + scenario 'visitor can leave the current game' do + click_button 'Sit', match: :first + #TODO: Fix race condition + sleep 4 + expect(page.text.scan('Guest').count).to eq 2 + click_button 'Stand', match: :first + expect(page).to have_content "Sit" + #TODO: Fix race condition + sleep 4 + expect(page.text.scan('Guest').count).to eq 0 + end + + end + + context "As a user" do + before(:each) do + @table = FactoryGirl.create :table + @user = FactoryGirl.create :user + signin(@user.email, @user.password) + visit table_path @table + end + + scenario 'user can sit at the table' do + #TODO: Fix race condition + expect(page).to have_content "Sit" + click_button 'Sit', match: :first + expect(page).to have_content @user.email + end + + scenario 'user can join the current game' do + expect(page).to have_content "Sit" + click_button 'Sit', match: :first + #TODO: Fix race condition. Trigger game react component reload when player joins a table + sleep 4 + expect(page).to have_content @user.email + expect(page.text.scan(@user.email).count).to eq 2 + end + + scenario 'user can leave the current game' do + expect(page).to have_content "Sit" + click_button 'Sit', match: :first + #TODO: Fix race condition + sleep 4 + expect(page.text.scan(@user.email).count).to eq 2 + click_button 'Stand', match: :first + expect(page).to have_content "Sit" + #TODO: Fix race condition + sleep 4 + expect(page.text.scan(@user.email).count).to eq 0 + end + + end +end diff --git a/spec/features/tables/table_index_spec.rb b/spec/features/tables/table_index_spec.rb index f706f56c..4177efce 100644 --- a/spec/features/tables/table_index_spec.rb +++ b/spec/features/tables/table_index_spec.rb @@ -26,7 +26,7 @@ feature 'Table index', :devise do # Then I see an index of tables scenario 'I can see all the tables', js: true do @user = FactoryGirl.create :user - signin(@user.email,'please123') + signin(@user.email,'password') 5.times { FactoryGirl.create :table } visit root_path diff --git a/spec/features/tables/table_join_spec.rb b/spec/features/tables/table_join_spec.rb index a282031e..d7057e6e 100644 --- a/spec/features/tables/table_join_spec.rb +++ b/spec/features/tables/table_join_spec.rb @@ -9,33 +9,70 @@ feature 'Table show', :devise do visit tables_path click_link @table.title end - # Scenario: Visitor can view a table - # Given I am not signed in - # When I visit a table - # Then I see the table attributes - scenario 'visitor can see the table', js: true do - expect(page).to have_selector "#table-#{@table.id} > h1", @table.title - end scenario 'visitor can sit at the table', js: true do - click_button 'Sit' + click_button 'Sit', match: :first expect(page).to have_content "Guest" end + + scenario 'visitor can join the current game', js: true do + click_button 'Sit', match: :first + #TODO: Fix race condition. Trigger game react component reload when player joins a table + sleep 4 + expect(page).to have_content "Guest" + expect(page.text.scan('Guest').count).to eq 2 + end + + scenario 'visitor can leave the current game', js: true do + click_button 'Sit', match: :first + #TODO: Fix race condition + sleep 4 + expect(page.text.scan('Guest').count).to eq 2 + click_button 'Stand', match: :first + expect(page).to have_content "Sit" + #TODO: Fix race condition + sleep 4 + expect(page.text.scan('Guest').count).to eq 0 + end + end context "As a user" do - # Scenario: User can view a table - # Given I am signed in - # When I click a table - # Then I see an index of tables - scenario 'visitor can see the table', js: true do + before(:each) do @table = FactoryGirl.create :table - # TODO: sign in - visit tables_path - click_link @table.title - expect(page).to have_button "Sit" - expect(page).to have_selector "#table-#{@table.id} > .title", @table.title - expect(current_path).to eq table_path(@table) + @user = FactoryGirl.create :user + signin(@user.email, @user.password) + visit table_path @table end + + scenario 'user can sit at the table', js: true do + #TODO: Fix race condition + expect(page).to have_content "Sit" + click_button 'Sit', match: :first + expect(page).to have_content @user.email + end + + scenario 'user can join the current game', js: true do + expect(page).to have_content "Sit" + click_button 'Sit', match: :first + #TODO: Fix race condition. Trigger game react component reload when player joins a table + sleep 4 + expect(page).to have_content @user.email + expect(page.text.scan(@user.email).count).to eq 2 + end + + scenario 'user can leave the current game', js: true do + expect(page).to have_content "Sit" + click_button 'Sit', match: :first + #TODO: Fix race condition + sleep 4 + expect(page.text.scan(@user.email).count).to eq 2 + click_button 'Stand', match: :first + expect(page).to have_content "Sit" + #TODO: Fix race condition + sleep 4 + expect(page.text.scan(@user.email).count).to eq 0 + end + end end diff --git a/spec/features/tables/table_show_spec.rb b/spec/features/tables/table_show_spec.rb new file mode 100644 index 00000000..0e3fb57a --- /dev/null +++ b/spec/features/tables/table_show_spec.rb @@ -0,0 +1,60 @@ +# Feature: Table show +# As a visitor +# I want to view a table +# So I can sit and play a game +feature 'Table show', :devise do + context "As a visitor" do + before(:each) do + @table = FactoryGirl.create :table + visit tables_path + click_link @table.title + end + # Scenario: Visitor can view a table + # Given I am not signed in + # When I visit a table + # Then I see the table attributes + scenario 'visitor can see the table', js: true do + expect(page).to have_selector "#table-#{@table.id} > h1", @table.title + end + + scenario 'visitor can see the seats', js: true do + #TODO: Race condition exists without have_content + expect(page).to have_content "Sit" + @seat_count = page.find_all(".seat").length + expect(@seat_count).to eq @table.seats.count + end + + scenario 'visitor can see the current game', js: true do + #TODO: Write a better expectation + expect(page).to have_content "Hand to beat" + end + end + + context "As a user" do + before(:each) do + @table = FactoryGirl.create :table + @user = FactoryGirl.create :user + signin(@user.email, @user.password) + end + + # Scenario: User can view a table + # Given I am signed in + # When I click a table + # Then I see the table attributes + scenario 'user can see the table', js: true do + visit tables_path + click_link @table.title + expect(page).to have_content "Sign out" + expect(page).to have_selector "#table-#{@table.id} > h1", @table.title + expect(current_path).to eq table_path(@table) + end + + scenario 'user can see the current game', js: true do + #TODO: Fix race condition + visit table_path @table + #TODO: Write a better expectation + expect(page).to have_content "Hand to beat" + end + + end +end diff --git a/spec/features/users/sign_in_spec.rb b/spec/features/users/sign_in_spec.rb index 7e50113a..cf6cb597 100644 --- a/spec/features/users/sign_in_spec.rb +++ b/spec/features/users/sign_in_spec.rb @@ -8,7 +8,7 @@ feature 'Sign in', :devise do # When I sign in with valid credentials # Then I see an invalid credentials message scenario 'user cannot sign in if not registered' do - signin('test@example.com', 'please123') + signin('test@example.com', 'password') expect(page).to have_content I18n.t 'devise.failure.not_found_in_database', authentication_keys: 'email' end diff --git a/spec/features/visitors/game_join_spec.rb b/spec/features/visitors/game_join_spec.rb index 2eba27a5..c6305172 100644 --- a/spec/features/visitors/game_join_spec.rb +++ b/spec/features/visitors/game_join_spec.rb @@ -20,4 +20,9 @@ feature 'Join game', :devise do @playerComponent = page.find("#player-1") expect(@playerComponent.text).to have_content("Guest#{@player.soft_token[0..5]}") end + + scenario 'visitor can join a game in progress' do + pending "To Be Implemented" + expect(page).to have_content "Waiting for next game." + end end diff --git a/spec/models/seat_spec.rb b/spec/models/seat_spec.rb index e9b20e78..0a0eb4f2 100644 --- a/spec/models/seat_spec.rb +++ b/spec/models/seat_spec.rb @@ -9,13 +9,31 @@ RSpec.describe Seat, type: :model do it { should respond_to :table } it { should respond_to :user } + it { should respond_to :user_id } + it { should respond_to :user_soft_token } it '#table returns a Table' do expect(@seat.table.class).to match Table end it '#user returns a User' do + @seat = FactoryGirl.create(:seat, :occupied) expect(@seat.user.class).to match User end + it '#user_id returns a Fixnum' do + @seat = FactoryGirl.create(:seat, :occupied) + expect(@seat.user_id.class).to match Fixnum + end + + it '#user_soft_token returns a String' do + @seat = FactoryGirl.create(:seat, :occupied) + expect(@seat.user_soft_token.class).to match String + end + + it "#occupied? returns a Boolean" do + expect(@seat.occupied?).to eq false + @seat.user_id = 1 + expect(@seat.occupied?).to eq true + end end diff --git a/spec/models/table_spec.rb b/spec/models/table_spec.rb index 4289029d..6fd39630 100644 --- a/spec/models/table_spec.rb +++ b/spec/models/table_spec.rb @@ -33,4 +33,8 @@ RSpec.describe Table, type: :model do expect(@table.current_game.class).to match Game end + it "#seats returns 4 seats by default" do + expect(@table.seats.count).to eq 4 + end + end diff --git a/spec/support/helpers/game_helpers.rb b/spec/support/helpers/game_helpers.rb index fac5a184..1862c103 100644 --- a/spec/support/helpers/game_helpers.rb +++ b/spec/support/helpers/game_helpers.rb @@ -7,10 +7,10 @@ module Features user2 = FactoryGirl.create :user user3 = FactoryGirl.create :user user4 = FactoryGirl.create :user - game.add_player_from_user user1 - game.add_player_from_user user2 - game.add_player_from_user user3 - game.add_player_from_user user4 + game.seats[0].sit user1 + game.seats[1].sit user2 + game.seats[2].sit user3 + game.seats[3].sit user4 game.start return game end