diff --git a/COMMENTS.md b/COMMENTS.md
new file mode 100644
index 0000000..09014f3
--- /dev/null
+++ b/COMMENTS.md
@@ -0,0 +1,4 @@
+# on view specs
+View specs are good for verifying page-elements and structure. However if we're doing page_objects as well we might as well use those rather than duplicating the effort.
+After all we put most of the structure into the page-object. If we had a way to specify what elements are always there (default in site-prism is ALL) we could easily make 90% of our view
+spec a simple verification of the page object `@page.all_there?`. This of course doesn't cover logic in the view but that's trivial with the page-object too.
diff --git a/Gemfile b/Gemfile
index 35e0fab..c8e7c33 100644
--- a/Gemfile
+++ b/Gemfile
@@ -50,9 +50,6 @@ group :development, :test do
# Access an IRB console on exception pages or by using <%= console %> in views
gem 'web-console', '~> 2.0'
- # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
- gem 'spring'
-
gem 'rspec-rails', '~> 3.0'
gem 'factory_girl_rails' # :development to help out in rails generators
diff --git a/Gemfile.lock b/Gemfile.lock
index 4b96e6a..c9fb452 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -232,7 +232,6 @@ GEM
addressable (>= 2.3.3, < 3.0)
capybara (>= 2.1, < 3.0)
slop (3.6.0)
- spring (1.3.6)
sprockets (3.3.3)
rack (~> 1.0)
sprockets-rails (2.3.2)
@@ -294,7 +293,6 @@ DEPENDENCIES
selenium-webdriver
shoulda-matchers
site_prism
- spring
sqlite3
turbolinks
uglifier (>= 1.3.0)
diff --git a/Guardfile b/Guardfile
index 60e3eae..9902825 100644
--- a/Guardfile
+++ b/Guardfile
@@ -24,7 +24,7 @@
# * zeus: 'zeus rspec' (requires the server to be started separately)
# * 'just' rspec: 'rspec'
-guard :rspec, cmd: "bundle exec rspec" do
+guard :rspec, cmd: "zeus rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
@@ -72,7 +72,8 @@ guard :rspec, cmd: "bundle exec rspec" do
end
end
-guard 'zeus' do
+# see issue: https://github.com/guard/guard-zeus/issues/30
+guard 'zeus', run_all: false, rspec: false do
require 'ostruct'
rspec = OpenStruct.new
@@ -86,11 +87,11 @@ guard 'zeus' do
# Ruby apps
ruby = OpenStruct.new
ruby.lib_files = /^(lib\/.+)\.rb$/
-
- watch(rspec.spec_files)
- watch(rspec.spec_helper) { rspec.spec_dir }
- watch(ruby.lib_files) { |m| rspec.spec.call(m[1]) }
-
+# see issue: https://github.com/guard/guard-zeus/issues/30
+# watch(rspec.spec_files)
+# watch(rspec.spec_helper) { rspec.spec_dir }
+# watch(ruby.lib_files) { |m| rspec.spec.call(m[1]) }
+
# Rails example
rails = OpenStruct.new
rails.app_files = /^app\/(.+)\.rb$/
diff --git a/README.rdoc b/README.md
similarity index 83%
rename from README.rdoc
rename to README.md
index a2f4fc9..551d0f2 100644
--- a/README.rdoc
+++ b/README.md
@@ -1,12 +1,14 @@
-== README
+# README
Helps teaching staff determine the best times for office hours by allowing students to input the times they cannot (or can) come to office hours.
Teaching staff can then elect to hold their office hours at times when more students are available. This maximizes the number of students able to get help and decreases the time wasted in office hours when no students attend.
-== OTHER
+# OTHER
This repo is also a repository for experiments with FullCalendar (http://fullcalendar.io/) and time conversion issues in Rails and moment.js.
In addition it contains an example of using site-prism and capybara with fullcalendar.
-== LICENSE
+See also [comments](COMMENTS.md) for interesting discoveries made throughout the project.
+
+# LICENSE
MIT
diff --git a/app/assets/images/calendar.png b/app/assets/images/calendar.png
new file mode 100644
index 0000000..a1b33f0
Binary files /dev/null and b/app/assets/images/calendar.png differ
diff --git a/app/assets/stylesheets/courses.scss b/app/assets/stylesheets/courses.scss
index 0cae8cc..e4795a5 100644
--- a/app/assets/stylesheets/courses.scss
+++ b/app/assets/stylesheets/courses.scss
@@ -1,3 +1,8 @@
// Place all the styles related to the courses controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
+
+img.mini-cal {
+ height: 100px;
+ margin-left: 20px;
+}
diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb
index 2f56b18..a015684 100644
--- a/app/helpers/courses_helper.rb
+++ b/app/helpers/courses_helper.rb
@@ -10,6 +10,32 @@ def as_a_student(courses)
as_a('student', courses)
end
+
+ #take a course and return something like:
+ # { students: [ { email: 'g@g.com', created_at ...} , ],
+ # educators: [ { email... } ],
+ # others: []
+ # }
+ def user_courses_by_type(course)
+ students = []
+ educators = []
+ others = []
+ course.course_participants.each do |cp|
+ case cp.role.role_name
+ when 'student'
+ students << cp.user
+ when 'educator'
+ educators << cp.user
+ else
+ others << cp.user
+ end
+ end
+ { students: students,
+ educators: educators,
+ others: others
+ }
+ end
+
private
def as_a(role_name, courses)
@@ -18,3 +44,5 @@ def as_a(role_name, courses)
end
end
end
+
+
diff --git a/app/views/courses/_participants_list.html.erb b/app/views/courses/_participants_list.html.erb
new file mode 100644
index 0000000..6d1f786
--- /dev/null
+++ b/app/views/courses/_participants_list.html.erb
@@ -0,0 +1,20 @@
+<% users = user_courses_by_type(@course).delete_if { |k,v| v.blank? } %>
+<% users.keys.each do |type| %>
+
<%= type %>
+
+
+
+ email |
+ |
+
+
+
+ <% users[type.to_sym].each do |participant| %>
+
+ <%= participant[:email] %> |
+
+ <% end %>
+
+
+<% end %>
+
diff --git a/app/views/courses/edit.html.erb b/app/views/courses/edit.html.erb
index 4e3e3a1..5873b2d 100644
--- a/app/views/courses/edit.html.erb
+++ b/app/views/courses/edit.html.erb
@@ -1,6 +1,23 @@
Editing Course
+Course information
<%= render 'form' %>
+
+
Scheduling
+ Optimal meeting times (calculated)
+ <%=image_tag('calendar.png', alt: 'optimal meeting times', class: 'mini-cal') %>
+ Edit my available times
+ <%= link_to course_calendar_path(@course) do %>
+ <%=image_tag('calendar.png', alt: 'optimal meeting times', class: 'mini-cal') %>
+ <% end %>
+
+
+
+
Course Participants
+ <%= render 'participants_list' %>
+
<%= link_to 'Show', @course %> |
<%= link_to 'Back', courses_path %>
+
+
diff --git a/bin/rails b/bin/rails
index 4d608ed..5191e69 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,8 +1,4 @@
#!/usr/bin/env ruby
-begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
-end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
diff --git a/bin/rake b/bin/rake
index 8017a02..1724048 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,8 +1,4 @@
#!/usr/bin/env ruby
-begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
-end
require_relative '../config/boot'
require 'rake'
Rake.application.run
diff --git a/bin/spring b/bin/spring
deleted file mode 100755
index 7b45d37..0000000
--- a/bin/spring
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env ruby
-
-# This file loads spring without using Bundler, in order to be fast.
-# It gets overwritten when you run the `spring binstub` command.
-
-unless defined?(Spring)
- require "rubygems"
- require "bundler"
-
- if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)
- Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq }
- gem "spring", match[1]
- require "spring/binstub"
- end
-end
diff --git a/spec/factories/course_participants.rb b/spec/factories/course_participants.rb
index 0b4c630..0f86d9d 100644
--- a/spec/factories/course_participants.rb
+++ b/spec/factories/course_participants.rb
@@ -2,6 +2,7 @@
factory :course_participant do
role
course
+ user
trait :student do
association :role, factory: :student_role
@@ -11,6 +12,10 @@
association :role, factory: :educator_role
end
+ trait :with_new_user do
+ association :user, factory: :user
+ end
+
factory :student_participant, traits: [:student]
factory :educator_participant, traits: [:educator]
end
diff --git a/spec/factories/courses.rb b/spec/factories/courses.rb
index 9d69bac..738ecb3 100644
--- a/spec/factories/courses.rb
+++ b/spec/factories/courses.rb
@@ -18,5 +18,39 @@
cp.save!
end
end
+
+ # huge factory that generates an entire stack of courses with users and roles
+ # please only use when really needed and in integration tests
+ factory :course_with_many_users do
+ transient { educators 2 }
+ transient { students 3 }
+
+ after(:create) do |course, evaluator|
+ educator_users = create_list(:educator_participant,
+ evaluator.educators.to_i,
+ :with_new_user,
+ course: course
+ )
+ student_users = create_list(:student_participant,
+ evaluator.students.to_i,
+ :with_new_user,
+ course: course
+ )
+ end
+ end
+ end
+ # build up a full deep hash from courses to users and roles
+ factory :course_with_many_users_hash, class: Hash do
+ transient { title 'Rocket Science' }
+ after(:build) do |course, evaluator|
+ participants = FactoryGirl.attributes_for_list(:student_role, 7)
+ participants += FactoryGirl.attributes_for_list(:educator_role, 2)
+ users = attributes_for_list(:user,9)
+ participants.each_with_index do |part, index|
+ part[:user] = users[index]
+ end
+ course[:course_participants] = participants
+ course[:title] = evaluator.title
+ end
end
end
diff --git a/spec/features/courses_editing_feature_spec.rb b/spec/features/courses_editing_feature_spec.rb
new file mode 100644
index 0000000..ce199b5
--- /dev/null
+++ b/spec/features/courses_editing_feature_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+RSpec.feature "Editing courses",
+ %q{
+ As a teacher or student,
+ I want to edit the courses I'm involved in,
+},
+ :js do
+
+
+ given!(:course) { FactoryGirl.create(:course_with_many_users, students: 3) } # a huge factory
+ given(:teacher) { course.course_participants.where( role: Role.educator ).first.user }
+ given(:one_student) { course.course_participants.where(role: Role.student)[2].user }
+
+ before do
+ UsersSignInPage.sign_in_user(teacher.email)
+ @p = CoursesIndexPage.new
+ @p.load
+ @p.teaching_courses.first.edit_link.click
+ @p = CoursesEditPage.new
+ end
+
+ it 'displays the page elements' do # verify the page-object ?
+ expect(@p).to be_displayed
+ expect(@p).to be_all_there
+ end
+
+ scenario "changing the course name" do
+ @p.title_field.set 'Saving Christmas'
+ @p.update_button.click
+ @p = CoursesIndexPage.new
+ # below is not implemented
+ #expect(@p).to be_displayed
+ #expect(@p.teaching_courses_table).to have_content 'Saving Christmas'
+ # instead we'll just check this for now:
+ expect(page).to have_content 'Saving Christmas'
+ end
+
+ scenario 'shows us the course participants broken into educators and students' do
+ expect(@p.students.count).to eq 3
+ expect(@p.educators.count).to eq 2
+ end
+
+ scenario 'can proceed to view my calendar' do
+ @p.edit_calendar_link.click
+ @p = CourseParticipantsPage.new
+ expect(@p).to be_displayed
+ end
+end
diff --git a/spec/helpers/courses_helper_spec.rb b/spec/helpers/courses_helper_spec.rb
index 7434d9e..98cfdb6 100644
--- a/spec/helpers/courses_helper_spec.rb
+++ b/spec/helpers/courses_helper_spec.rb
@@ -71,4 +71,26 @@
expect(res.count).to eq 3
end
end
+
+ describe '#user_courses_by_role' do
+ let(:student) { FactoryGirl.create(:user) }
+ let(:teacher) { FactoryGirl.create(:user) }
+ let(:course) { FactoryGirl.create(:course_for_user, user: student) }
+ before { FactoryGirl.create(:educator_participant, course: course, user: teacher) }
+ subject(:result) { helper.user_courses_by_type(course) }
+ it 'returns a hash with keys :students, :teachers, :other' do
+ expect(result).to be_a Hash
+ expect(result.keys) =~ [ :students, :teachers, :other ]
+ end
+ it 'has an array of students in the students hash' do
+ expect(result[:students]).to be_an Array
+ expect(result[:students].first.email).to eq student.email
+ end
+
+ it 'has an array of educators under the educators keys' do
+ expect(result[:educators]).to be_an Array
+ expect(result[:educators].first.email).to eq teacher.email
+ end
+
+ end
end
diff --git a/spec/page_objects/pages/course_participants_page.rb b/spec/page_objects/pages/course_participants_page.rb
index 3bc36cf..81b7f7d 100644
--- a/spec/page_objects/pages/course_participants_page.rb
+++ b/spec/page_objects/pages/course_participants_page.rb
@@ -1,6 +1,8 @@
class CourseParticipantsPage < SitePrism::Page
# set_url 'course_participants/{:id}/'
set_url '/courses/{:course_id}/calendar'
+ set_url_matcher /courses.*\/calendar/
+
section :calendar, WeekCalendarSection, '#calendar'
element :save_button, 'input[name="commit"]'
@@ -17,3 +19,4 @@ def self.navigate_here
page.navigate_here
end
end
+
diff --git a/spec/page_objects/pages/courses_edit_page.rb b/spec/page_objects/pages/courses_edit_page.rb
new file mode 100644
index 0000000..4704857
--- /dev/null
+++ b/spec/page_objects/pages/courses_edit_page.rb
@@ -0,0 +1,14 @@
+class CoursesEditPage < SitePrism::Page
+ set_url '/courses/{course_id}/edit'
+ set_url_matcher /courses.*\/edit/
+ element :title_field, 'input#course_title'
+ element :update_button, 'input[name="commit"]'
+
+ element :edit_calendar_link, 'div#scheduling a'
+ element :educators_table, 'table.type-educators'
+ elements :educators, 'table.type-educators tbody tr'
+
+ element :students_table, 'table.type-students'
+ elements :students, 'table.type-students tbody tr'
+
+end
diff --git a/spec/support/capybara_string.rb b/spec/support/capybara_string.rb
new file mode 100644
index 0000000..a58d54d
--- /dev/null
+++ b/spec/support/capybara_string.rb
@@ -0,0 +1,4 @@
+def capybara(some_str=nil)
+ some_str = rendered if some_str.nil?
+ Capybara.string(some_str)
+end
diff --git a/spec/views/courses/edit.html.erb_spec.rb b/spec/views/courses/edit.html.erb_spec.rb
index 78d974d..b9402a0 100644
--- a/spec/views/courses/edit.html.erb_spec.rb
+++ b/spec/views/courses/edit.html.erb_spec.rb
@@ -2,17 +2,69 @@
RSpec.describe "courses/edit", type: :view do
before(:each) do
- @course = assign(:course, Course.create!(
- :title => "MyString"
- ))
+ @course = FactoryGirl.build_stubbed(:course, title: 'MyString')
+ assign :course, @course
+ users = { students: FactoryGirl.attributes_for_list(:user, 7),
+ educators: FactoryGirl.attributes_for_list(:user, 2)
+ }
+ allow(view).to receive(:user_courses_by_type).and_return(users)
end
it "renders the edit course form" do
render
-
assert_select "form[action=?][method=?]", course_path(@course), "post" do
-
assert_select "input#course_title[name=?]", "course[title]"
end
end
+
+ it 'has a Scheduling section' do
+ render
+ assert_select 'div#scheduling' do
+ assert_select 'h2', text: 'Scheduling'
+ end
+ end
+
+ it 'has a Participants list' do
+ render
+
+ assert_select 'div#participants' do
+ assert_select 'h2', text: 'Course Participants'
+ end
+ end
+
+ describe 'scheduling section' do
+ it 'has two calendar images' do
+ render
+ assert_select 'div#scheduling img', count: 2
+ end
+ it 'has a link to calendar page' do
+ render
+ expect(capybara.find('div#scheduling a')['href']).to match course_calendar_path(@course)
+ end
+ end
+
+ describe 'participants section' do
+
+ it 'has a list of students' do
+ render
+ assert_select 'div#participants' do
+ assert_select 'h3', text: 'students'
+ assert_select 'table.type-students tbody tr', count: 7
+ end
+ end
+ it 'has a list of teachers' do
+ render
+ assert_select 'div#participants' do
+ assert_select 'h3', text: 'educators'
+ assert_select 'table.type-educators tbody tr', count: 2
+ end
+ end
+ it "doesn't display sections for which there are no entries" do
+ allow(view).to receive(:user_courses_by_type).and_return({students: [], other: []})
+ render
+
+ expect(rendered).to_not have_selector('table.type-students')
+ expect(rendered).to_not have_selector('table.type-educators')
+ end
+ end
end