Open kkerley opened 6 years ago
Could you share a copy of your Gemfile.lock as it exists to this point, along with your Ruby version (ruby --version
)? Are you on Windows/Mac/Linux/something else? Maybe there's a discrepancy somewhere.
I'm on macOS 10.13.6 using Ruby 2.5.1p57 through RVM.
Gemfile.lock:
GIT
remote: https://github.com/thoughtbot/shoulda-matchers.git
revision: 4b160bd19ecca7f97d7ac22dccd5fde9b0da5a9f
branch: rails-5
specs:
shoulda-matchers (3.1.2)
activesupport (>= 4.2.0)
GEM
remote: https://rubygems.org/
specs:
actioncable (5.2.1)
actionpack (= 5.2.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailer (5.2.1)
actionpack (= 5.2.1)
actionview (= 5.2.1)
activejob (= 5.2.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.2.1)
actionview (= 5.2.1)
activesupport (= 5.2.1)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.1)
activesupport (= 5.2.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.2.1)
activesupport (= 5.2.1)
globalid (>= 0.3.6)
activemodel (5.2.1)
activesupport (= 5.2.1)
activerecord (5.2.1)
activemodel (= 5.2.1)
activesupport (= 5.2.1)
arel (>= 9.0)
activestorage (5.2.1)
actionpack (= 5.2.1)
activerecord (= 5.2.1)
marcel (~> 0.3.1)
activesupport (5.2.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
archive-zip (0.11.0)
io-like (~> 0.3.0)
arel (9.0.0)
autoprefixer-rails (9.1.4)
execjs
bcrypt (3.1.12)
bindex (0.5.0)
bootstrap-sass (3.3.7)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
builder (3.2.3)
byebug (10.0.2)
capybara (2.15.4)
addressable
mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
chromedriver-helper (2.0.0)
archive-zip (~> 0.10)
nokogiri (~> 1.8)
climate_control (0.2.0)
coffee-rails (4.2.2)
coffee-script (>= 2.2.0)
railties (>= 4.0.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
concurrent-ruby (1.0.5)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
devise (4.5.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 6.0)
responders
warden (~> 1.2.3)
diff-lcs (1.3)
erubi (1.7.1)
execjs (2.7.0)
factory_bot (4.10.0)
activesupport (>= 3.0.0)
factory_bot_rails (4.10.0)
factory_bot (~> 4.10.0)
railties (>= 3.0.0)
faker (1.9.1)
i18n (>= 0.7)
ffi (1.9.25)
geocoder (1.5.0)
globalid (0.4.1)
activesupport (>= 4.2.0)
hashdiff (0.3.7)
i18n (1.1.0)
concurrent-ruby (~> 1.0)
io-like (0.3.0)
jbuilder (2.7.0)
activesupport (>= 4.2.0)
multi_json (>= 1.2)
jquery-rails (4.3.3)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
launchy (2.4.3)
addressable (~> 2.3)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
loofah (2.2.2)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.0)
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
method_source (0.9.0)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812)
mimemagic (0.3.2)
mini_mime (1.0.1)
mini_portile2 (2.3.0)
minitest (5.11.3)
multi_json (1.13.1)
nio4r (2.3.1)
nokogiri (1.8.4)
mini_portile2 (~> 2.3.0)
orm_adapter (0.5.0)
paperclip (6.1.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
mime-types
mimemagic (~> 0.3.0)
terrapin (~> 0.6.0)
public_suffix (3.0.3)
puma (3.12.0)
rack (2.0.5)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.1)
actioncable (= 5.2.1)
actionmailer (= 5.2.1)
actionpack (= 5.2.1)
actionview (= 5.2.1)
activejob (= 5.2.1)
activemodel (= 5.2.1)
activerecord (= 5.2.1)
activestorage (= 5.2.1)
activesupport (= 5.2.1)
bundler (>= 1.3.0)
railties (= 5.2.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
railties (5.2.1)
actionpack (= 5.2.1)
activesupport (= 5.2.1)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
rake (12.3.1)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
responders (2.4.0)
actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3)
rspec-core (3.8.0)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-rails (3.8.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.0)
ruby_dep (1.5.0)
rubyzip (1.2.2)
safe_yaml (1.0.4)
sass (3.5.7)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sass-rails (5.0.7)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
selenium-webdriver (3.14.0)
childprocess (~> 0.5)
rubyzip (~> 1.2)
spring (2.0.2)
activesupport (>= 4.2)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
thor (0.20.0)
thread_safe (0.3.6)
tilt (2.0.8)
turbolinks (5.2.0)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (4.1.19)
execjs (>= 0.3.0, < 3)
vcr (4.0.0)
warden (1.2.7)
rack (>= 1.0)
web-console (3.7.0)
actionview (>= 5.0)
activemodel (>= 5.0)
bindex (>= 0.4.0)
railties (>= 5.0)
webmock (3.4.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
websocket-driver (0.7.0)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
xpath (2.1.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
bootstrap-sass
byebug
capybara (~> 2.15.4)
chromedriver-helper
coffee-rails (~> 4.2)
devise
factory_bot_rails (~> 4.10.0)
faker
geocoder
jbuilder (~> 2.5)
jquery-rails
launchy (~> 2.4.3)
listen (>= 3.0.5, < 3.2)
paperclip
puma (~> 3.7)
rails (~> 5.2.1)
rspec-rails (~> 3.8.0)
sass-rails (~> 5.0)
selenium-webdriver
shoulda-matchers!
spring
spring-commands-rspec
spring-watcher-listen (~> 2.0.0)
sqlite3
turbolinks (~> 5)
tzinfo-data
uglifier (>= 1.3.0)
vcr
web-console (>= 3.3.0)
webmock
BUNDLED WITH
1.16.4
And I used this branch: https://github.com/everydayrails/everydayrails-rspec-2017/tree/01-untested
as my starting point so I'd be the one writing most of the code rather than looking at completed code. I believe a few gems have been updated from the versions in the book, but that doesn't seem like anything that should be affecting Rails outputting for
attributes on labels to properly associate the label and input.
It's happening again with the subsequent test to check a task and see that it completed and then unchecking to see that it's back to be incomplete (pages 101-103).
scenario "user toggles a task", js: true do
user = FactoryBot.create(:user)
project = FactoryBot.create(:project, name: "RSpec tutorial", owner: user)
task = project.tasks.create!(name: "Finish RSpec tutorial")
visit root_path
click_link "Sign in"
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
click_link "RSpec tutorial"
# check "Finish RSpec tutorial"
check "completed"
save_and_open_page
expect(page).to have_css "label#task_#{task.id}.completed"
expect(task.reload).to be_completed
# uncheck "Finish RSPec tutorial"
uncheck "completed"
expect(page).to_not have_css "label#task_#{task.id}.completed"
expect(task.reload).to_not be_completed
end # scenario "user toggles a task"
I've done some subsequent research and it appears this is an issue with form_with
(and the Devise login form uses form_for
which is outputting correctly. I found this thread discussing the same basic issues I'm having and it links to other Github threads but if there was a resolution, it doesn't seem to be working in 5.2.1. I haven't used Rails since 3.2 so this is all very new to me and I'm not sure I understand the differences between form_for
and form_with
, nor am I able to successfully change over the forms from the Everyday Rails app.
So in the code above, I changed the check
and uncheck
commands to use "completed" since that's the name of the checkbox, but my concern regarding both this book and my own applications after I finish this is that I'm going to end up in a much more complex/complicated situation with multiple elements that have the same names because it's a SPA, and tests are going to fail endlessly because I can't get more specific.
I guess that's where the scoping could come into play but given how weird form_with
behaves, what if there aren't unique IDs I can use to get more specific? Having to manually test in the browser in the dev environment defeats the entire purpose of doing automated TDD like this.
I was planning to dig into this over the weekend, but at what point did you switch to 5.2? The app is built and tested on 5.1. I always recommend having a test suite in place before upgrading Rails versions.
I finished the chapter and am curious about the output from adding additional tasks to this feature spec for completing/un-completing tasks:
I made four tasks total:
task1 = project.tasks.create!(name: "Finish RSpec tutorial")
task2 = project.tasks.create!(name: "Add additional tasks")
task3 = project.tasks.create!(name: "Make sure additional tasks can be completed")
task4 = project.tasks.create!(name: "Use Capybara context")
And all are being output as expected:
<tbody>
<tr class="task">
<td>
<label id="task_1" class="">
<input type="checkbox" name="completed" id="completed" value="1" data-remote="true" data-url="/projects/1/tasks/1/toggle" data-method="post" style="font-size: 300%">
Finish RSpec tutorial
</label>
</td>
<td class="task-actions">
<a class="btn btn-xs btn-default" href="/projects/1/tasks/1/edit">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
Edit
</a> <a data-confirm="Are you sure?" class="btn btn-default btn-xs" rel="nofollow" data-method="delete" href="/projects/1/tasks/1">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
Delete
</a> </td>
</tr>
<tr class="task">
<td>
<label id="task_2" class="">
<input type="checkbox" name="completed" id="completed" value="2" data-remote="true" data-url="/projects/1/tasks/2/toggle" data-method="post" style="font-size: 300%">
Add additional tasks
</label>
</td>
<td class="task-actions">
<a class="btn btn-xs btn-default" href="/projects/1/tasks/2/edit">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
Edit
</a> <a data-confirm="Are you sure?" class="btn btn-default btn-xs" rel="nofollow" data-method="delete" href="/projects/1/tasks/2">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
Delete
</a> </td>
</tr>
<tr class="task">
<td>
<label id="task_3" class="">
<input type="checkbox" name="completed" id="completed" value="3" data-remote="true" data-url="/projects/1/tasks/3/toggle" data-method="post" style="font-size: 300%">
Make sure additional tasks can be completed
</label>
</td>
<td class="task-actions">
<a class="btn btn-xs btn-default" href="/projects/1/tasks/3/edit">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
Edit
</a> <a data-confirm="Are you sure?" class="btn btn-default btn-xs" rel="nofollow" data-method="delete" href="/projects/1/tasks/3">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
Delete
</a> </td>
</tr>
<tr class="task">
<td>
<label id="task_4" class="">
<input type="checkbox" name="completed" id="completed" value="4" data-remote="true" data-url="/projects/1/tasks/4/toggle" data-method="post" style="font-size: 300%">
Use Capybara context
</label>
</td>
<td class="task-actions">
<a class="btn btn-xs btn-default" href="/projects/1/tasks/4/edit">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
Edit
</a> <a data-confirm="Are you sure?" class="btn btn-default btn-xs" rel="nofollow" data-method="delete" href="/projects/1/tasks/4">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
Delete
</a> </td>
</tr>
</tbody>
But each task has the same name="completed"
and id="completed"
attributes. I know I can use context to specify by within "#task_1" do ...
but that just seems bad to have the same name
and id
for each one of them. I looked at /views/tasks/_task.html.erb
and see:
<label id="<%= dom_id(task) %>" class="<%= task.completed? ? 'completed' : nil %>">
<%= check_box_tag 'completed', task.id, task.completed,
data: {
remote: true,
url: toggle_project_task_path(task.project, task),
method: :post
},
style: "font-size: 300%"
%>
<%= task.name %>
</label>
It's never a good idea to have duplicate id
s on a page and when I try to change _task.html.erb to:
<%= check_box_tag "task_#{task.id}_completed", task.id, task.completed,
data: {
remote: true,
url: toggle_project_task_path(task.project, task),
method: :post
},
style: "font-size: 300%"
%>
The test always fails because it can't find tasks using that ID structure. When I try to save_and_open_page
to see what's wrong and what's being output, there are no checkboxes on the page at all.
So we really have to have the same id
for every checkbox?
I was planning to dig into this over the weekend, but at what point did you switch to 5.2? The app is built and tested on 5.1. I always recommend having a test suite in place before upgrading Rails versions.
I've been using Rails 5.2 the entire time.
For the purposes of this book, I think you'll have a better time with it if you stick to gem versions as defined in the sample code's Gemfile.lock, including Rails 5.1. My approach to learning TDD is to write. tests for code that's already been browser-tested. Once you're more comfortable writing tests, flip it around to a test-first approach that typically comes with TDD (see chapter 11).
I can't guarantee that newer versions of Rails or any other dependencies will work with the sample app. Due to the process and time commitment involved with building up the sample app and tests chapter by chapter, I don't usually redo everything for every minor-level Rails release.
But once a test suite is in place for an earlier version of Rails, it can help with the upgrade process for Rails or any other dependency. This is the approach I take with real-world, production apps.
So we really have to have the same
id
for every checkbox?
You're right, this is an issue with the code that something in my version of the dependencies doesn't catch, but maybe something in Rails 5.2 is less permissive. (I first suspected a difference in Capybara versions, but that doesn't appear to be the case.)
You could fix the application code with something like
diff --git a/app/views/tasks/_task.html.erb b/app/views/tasks/_task.html.erb
index 0b420ae..c63897e 100644
--- a/app/views/tasks/_task.html.erb
+++ b/app/views/tasks/_task.html.erb
@@ -1,7 +1,7 @@
<tr class="task">
<td>
<label id="<%= dom_id(task) %>" class="<%= task.completed? ? 'completed' : nil %>">
- <%= check_box_tag 'completed', task.id, task.completed,
+ <%= check_box_tag dom_id(task), task.id, task.completed,
data: {
remote: true,
url: toggle_project_task_path(task.project, task),
But I would also be curious to see how things behave by basing the sample app on Rails 5.1.
For the sake of completing the book, I've just moved forward by using the Rails-generated names of fields instead of trying to find them by their label text value. Such an odd discrepancy in Rails.
The rest of the book has been great and extremely helpful. I stopped getting hung up on the Capybara stuff because I remembered I'll be doing the front end of any projects in React, so the Capybara functionality would be skipped in favor of React testing via Jest and Enzyme.
For what it's worth, I've made a few changes to the test suites to account for the output differences between 5.2 and 5.1 (the original functions can be found on page 142 and 143):
def complete_task(labelID, name)
within labelID do
check name
end
end # complete_task
def undo_complete_task(labelID, name)
within labelID do
uncheck name
end
end # undo_complete_task
def expect_complete_task(labelCSS, task)
aggregate_failures do
expect(page).to have_css labelCSS
expect(task.reload).to be_completed
end
end # expect_complete_task
def expect_incomplete_task(labelCSS, task)
aggregate_failures do
expect(page).to_not have_css labelCSS
expect(task.reload).to_not be_completed
end
end # expect_incomplete_task
And then the corresponding scenarios were changed to:
scenario "user toggles task1", js: true do
# using our custom login helper:
# sign_in_as user
# or the one provided by Devise:
sign_in user
go_to_project "RSpec tutorial"
complete_task("label#task_1", "completed")
expect_complete_task("label#task_1.completed", task1)
undo_complete_task("label#task_1", "completed")
expect_incomplete_task("label#task_1.completed", task1)
end # scenario "user toggles task1"
scenario "user completes task4", js: true do
# using our custom login helper:
# sign_in_as user
# or the one provided by Devise:
sign_in user
go_to_project "RSpec tutorial"
complete_task("label#task_4", "completed")
expect_complete_task("label#task_4.completed", task4)
end # scenario "user toggles task4"
scenario "user toggles task3", js: true do
# using our custom login helper:
# sign_in_as user
# or the one provided by Devise:
sign_in user
go_to_project "RSpec tutorial"
complete_task("label#task_3", "completed")
expect_complete_task("label#task_3.completed", task3)
undo_complete_task("label#task_3", "completed")
expect_incomplete_task("label#task_3.completed", task3)
end # scenario "user toggles task3"
(I expanded on the book examples by adding 3 more tasks and updating their variable names accordingly).
This works and hopefully it can be helpful for someone else in the future should they get stuck on this.
Thanks for writing the book and finally making TDD make sense!
For anyone googling: the way i solved this problem is by passing for: :my-desired-id
as part of the options hash to the call to f.label
. so if you were doing this example, you'd write:
<div class="form-group">
<%= f.label :name, for: :project_name %>
<%= f.text_field :name, class: "form-control", id: :project_name %>
</div>
I am completely baffled by this and the fact I cannot find any information about how to do this is downright infuriating.
Using the following scenario from pages 95-96:
I repeatedly get:
As you can see above, I moved a little further along in the chapter and added in the
save_and_open_page
code afterclick_link "New Project"
to see what is going on. And this is the HTML that's output on the page:So the label and field are there as expected. And I then looked at the project/_form.html.erb file to confirm that it does, in fact, associate the labels and fields:
Yet, there's no
for
attribute on either rendered HTML label associating the label with the field the label is getting its text value from. And I can find absolutely nothing explaining what magic is needed to make that happen. Rails docs show examples of fields setup the exact same way with the correspondingfor
attribute output on thelabel
yet not on this form. And when I move thesave_and_open_page
line up underclick_link "Sign in"
(since there's no issue getting that form filled in and moving on), I see this rendered HTML:which comes from this code found in views/devise/sessions/new.html.erb:
So somehow, basically identical code is outputting differently for no reason I can discern. And because of this, it's making the provided test scenario in the book impossible to follow because it doesn't behave as expected.
Given that on page 101 under the Testing JavaScript interactions header it says "So we’ve verified, with a passing spec, that our user interface for adding projects is working as planned." either something is totally different with how Rails works since the book was last updated, or something in the code or book or both necessary to make this work as presented in the book is missing.
I've found through Stack Overflow that changing:
to:
Works and I've confirmed it does and the tests pass since those are the actual
name
s of the fields, but that's not the point: I shouldn't have to do that because these labels should have a properfor
attribute on them, yet they do not and it's seemingly impossible to get that attribute on the Rails-generated label.This is the exact kind of stuff that has happened every single time I've attempted to learn TDD and is the exact reason I ultimately gave up on Rails four years ago after using it for 9 years. Simple stuff that shouldn't even be a thought doesn't work the way the book/guide/video I'm following says it should and instead of writing tests that are actually confirming functionality and providing peace of mind, I'm trying to figure out why something so painfully basic and completely unrelated to the test isn't happening and thus causing the test to fail.