Permalink
Browse files

Implemented Scenario: Assigning an existing story to a user

  • Loading branch information...
1 parent e5e70bd commit 5dfce43a2d8b4813d6e7d95f4a6502c7bf293bce @bkeepers committed Sep 1, 2011
@@ -7,6 +7,8 @@
var ApplicationController = Spine.Controller.create({
init: function() {
+ User.fetch();
+
StoryListController.init({el: $('#container')});
NewStoryController.init({el: $('article')});
@@ -0,0 +1,7 @@
+// handlebars doesn't invoke properties that are functions. This is an ugly hack to do that
+Handlebars.registerHelper('f', function(fn, block) {
+ var val = fn.apply(this);
+ if(val) {
+ return typeof block == 'function' ? block(val) : val;
+ }
+});
@@ -0,0 +1,11 @@
+Handlebars.registerHelper('users', function(fn) {
+ var users = User.all(), result = '';
+
+ for(var i in users) {
+ user = users[i];
+ if(this.owner_email == user.email) { user.selected = true; }
+ result += fn(user);
+ }
+
+ return result;
+});
@@ -1,4 +1,4 @@
-var Story = Spine.Model.setup('Story', 'description', 'status', 'position');
+var Story = Spine.Model.setup('Story', 'description', 'status', 'position', 'owner_email');
Story.extend(Spine.Model.Ajax).extend({
url: '/stories'
@@ -13,6 +13,10 @@ Story.include({
prev = prev || 0;
next = next || 1;
this.position = (next - prev) / 2 + prev;
+ },
+
+ owner: function() {
+ return User.findByAttribute('email', this.owner_email);
}
});
@@ -0,0 +1,3 @@
+var User = Spine.Model.setup('User', 'name', 'email', 'initials');
+
+User.extend(Spine.Model.Ajax);
@@ -37,13 +37,23 @@
}
}
+ .owner {
+ font-weight:bold;
+ color: #000;
+ padding-left:0.25em;
+ text-shadow:none;
+ }
+
&.approved {
background-color: rgb(238,238,238);
color: rgb(150,150,150);
a:hover {
background-color: rgb(235, 235, 235);
}
+ .owner {
+ color:inherit;
+ }
}
&.active, &.active a:hover {
@@ -56,6 +66,10 @@
border-bottom:1px solid rgb(5,110,178);
border-top: 1px solid rgb(25,130,208);
margin-top: -1px;
+
+ .owner {
+ color: inherit;
+ }
}
form {
View
@@ -0,0 +1,20 @@
+@javascript
+Feature: Assigning stories to users
+
+ Scenario: Assigning an existing story to a user
+ Given the following user exists:
+ | name | email |
+ | Chuck Bartowski | charles@bartowski.com |
+ Given the following story exists:
+ | description |
+ | Story can be assigned to a user |
+ | Not assigned to me |
+ When I go to the home page
+ And I follow "Story can be assigned to a user"
+ And I select "Chuck Bartowski" from "Owned by" within the story editor
+ And I press "Save" within the story editor
+ Then I should see "CB" within the story "Story can be assigned to a user"
+
+ Scenario: Assigning a new story to a user
+ Scenario: Starting an unassigned story assigns it
+ Scenario: Starting an assigned story does not assign it
View
@@ -18,7 +18,7 @@
Capybara.javascript_driver = ENV['SELENIUM'] ? :selenium : :webkit
Before do
- Gaskit::Story.store.clear
+ Gaskit.repo.git.fs_delete("refs/heads/gaskit")
end
Capybara.register_driver :selenium do |app|
@@ -11,7 +11,7 @@ def selector_for(locator)
when "the page"
"html > body"
when "the story editor"
- "article"
+ "#editor"
when "the story list"
"#stories"
when /^the story "(.+)"$/
View
@@ -18,3 +18,4 @@ def self.repo
end
require 'gaskit/story'
+require 'gaskit/user'
View
@@ -56,6 +56,11 @@ def asset_path(source)
:ok
end
+ get '/users' do
+ User.ensure_i_exist
+ json User.all
+ end
+
private
def json(data)
View
@@ -6,6 +6,7 @@ class Story
store :git, Gaskit.repo, :branch => 'gaskit', :path => 'stories'
attribute :description, String
+ attribute :owner_email, String
attribute :status, String, :default => 'pending'
attribute :position, Float
View
@@ -0,0 +1,55 @@
+module Gaskit
+ class User
+ attr_accessor :name, :email, :initials
+
+ def initialize(attrs = {})
+ attrs.each {|key, value| self.send("#{key}=", value) }
+ set_default_initials
+ end
+
+ def self.adapter
+ Adapter[:git].new(Gaskit.repo, :branch => 'gaskit')
+ end
+
+ def self.all
+ adapter.get('users') || []
+ end
+
+ def self.me
+ new(:name => Gaskit.repo.config['user.name'], :email => Gaskit.repo.config['user.email'])
+ end
+
+ def self.ensure_i_exist
+ me.save unless all.include?(me)
+ end
+
+ def save
+ set_default_initials
+ self.class.adapter.set('users', self.class.all.push(self).uniq.sort)
+ end
+ alias save! save
+
+ def <=>(other)
+ name <=> other.name
+ end
+
+ def ==(other)
+ other.is_a?(self.class) && email == other.email
+ end
+
+ def eql?(other)
+ self == other
+ end
+
+ def hash
+ email.hash
+ end
+
+ private
+
+ def set_default_initials
+ self.initials ||= name.split(' ').map {|word| word[0, 1] }.join if name.present?
+ end
+
+ end
+end
View
@@ -2,4 +2,9 @@
factory :story, :class => Gaskit::Story do
sequence(:description) {|i| "Story #{i}" }
end
+
+ factory :user, :class => Gaskit::User do
+ sequence(:name) {|i| "User #{i}" }
+ sequence(:email) {|i| "user#{i}@example.com" }
+ end
end
View
@@ -98,4 +98,20 @@ def app
Story.key?(@story.id).should be_false
end
end
+
+ context 'GET /users' do
+ subject do
+ get '/users'
+ end
+
+ it 'should be successful' do
+ subject.status.should == 200
+ end
+
+ it 'should render json' do
+ json = ActiveSupport::JSON.decode(subject.body)
+ json.length.should == 1
+ json[0]['name'].should == User.me.name
+ end
+ end
end
View
@@ -28,6 +28,11 @@
story.destroy
Story.count.should == 0
end
+
+ it 'should not include users' do
+ User.ensure_i_exist
+ Story.count.should == 0
+ end
end
describe '.all' do
@@ -38,5 +43,10 @@
story.destroy
Story.all.should == []
end
+
+ it 'should not include users' do
+ User.ensure_i_exist
+ Story.all.should == []
+ end
end
end
View
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe User do
+ before do
+ User.all.clear
+ User.adapter.delete('users')
+ end
+
+ it "should be uniq-able" do
+ u1 = User.new(:name => 'Morgan', :email => 'morgan@buymore.com')
+ u2 = User.new(:name => 'Morgan', :email => 'morgan@buymore.com')
+ [u1, u2].uniq.size.should == 1
+ end
+
+ it 'should set initials' do
+ User.new(:name => 'Ellie Bartowski').initials.should == 'EB'
+ end
+
+ describe 'save' do
+ it 'should add user to git' do
+ user = User.new(:name => "Sarah Walker", :email => "sarah@cia.gov")
+ user.save
+ users = User.adapter.get('users')
+ users.size.should == 1
+ users.first.should == user
+ end
+ end
+
+ describe '.all' do
+ it 'should default to array' do
+ User.all.should == []
+ end
+
+ it 'should properly load existing users' do
+ User.adapter.set('users', [
+ User.new(:name => 'Chuck'),
+ User.new(:name => 'Morgan'),
+ User.new(:name => 'Sarah')
+ ])
+ users = User.all
+ users.size.should == 3
+ users[0].should be_instance_of(User)
+ users[0].name.should == 'Chuck'
+ end
+
+ end
+
+ describe '.me' do
+ it 'should return a user' do
+ user = User.me
+ user.should be_instance_of(User)
+ user.name.should_not be_blank
+ user.email.should_not be_blank
+ end
+ end
+
+ describe 'equality' do
+ it 'should be true if name and email are the same' do
+ a = User.new(:name => 'Jon', :email => 'a@b.com')
+ b = User.new(:name => 'Jon', :email => 'a@b.com')
+ a.should == b
+ end
+
+ it 'should be true even if name is different' do
+ a = User.new(:name => 'Jon', :email => 'a@b.com')
+ b = User.new(:name => 'Dave', :email => 'a@b.com')
+ a.should == b
+ end
+
+ it 'should be false if email is different' do
+ a = User.new(:name => 'Jon', :email => 'a@b.com')
+ b = User.new(:name => 'Jon', :email => 'x@y.com')
+ a.should_not == b
+ end
+ end
+
+ describe "hash" do
+ it "should be same for objects with same email" do
+ User.new(:email => 'a').hash.should == User.new(:email => 'a').hash
+ end
+
+ it "should not be same for objects with different email" do
+ User.new(:email => 'a').hash.should_not == User.new(:email => 'b').hash
+ end
+ end
+end
@@ -22,6 +22,16 @@
</p>
</section>
+ <section>
+ <label for="story_owner">Owned by</label>
+ <select id="story_owner" name="owner_email">
+ <option>nobody</option>
+ {{#users}}
+ <option value="{{email}}" {{#selected}} selected="selected"{{/selected}}>{{name}}</option>
+ {{/users}}
+ </select>
+ </section>
+
<h2>Discuss</h2>
<section>
<ul class="discuss">
@@ -2,6 +2,10 @@
<a href="#/stories/{{id}}">
{{title}}
+ {{#f owner }}
+ <span class="owner" title="{{name}}">{{initials}}</span>
+ {{/f}}
+
<form>
{{#isPending}}
<button name="status" value="started" class="start">Start</button>

0 comments on commit 5dfce43

Please sign in to comment.