diff --git a/.rubocop.yml b/.rubocop.yml index 59a473e8ee..e122960322 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -43,3 +43,30 @@ PerlBackrefs: # We probably can refactor the backref out, but for now excluding it since # we can't use named matches in 1.8.7 - lib/generators/rspec/scaffold/scaffold_generator.rb + +Style/AccessModifierDeclarations: + Enabled: false + +Naming/MemoizedInstanceVariableName: + Enabled: false + +Naming/UncommunicativeMethodParamName: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false + +Metrics/BlockLength: + Enabled: false + +Lint/AssignmentInCondition: + Enabled: false + +Lint/EmptyExpression: + Enabled: false + +Layout/AlignHash: + Enabled: false + +Naming/RescuedExceptionsVariableName: + Enabled: false diff --git a/.rubocop_rspec_base.yml b/.rubocop_rspec_base.yml index af024e8c53..045f1799cd 100644 --- a/.rubocop_rspec_base.yml +++ b/.rubocop_rspec_base.yml @@ -22,7 +22,7 @@ CaseEquality: Enabled: false # Warns when the class is excessively long. -ClassLength: +Metrics/ClassLength: Max: 100 CollectionMethods: @@ -66,7 +66,7 @@ LineLength: Max: 100 # Over time we'd like to get this down, but this is what we're at now. -MethodLength: +Metrics/MethodLength: Max: 15 # Who cares what we call the argument for binary operator methods? @@ -115,7 +115,10 @@ StringLiterals: Style/SpecialGlobalVars: Enabled: false -Style/TrailingCommaInLiteral: +Style/TrailingCommaInArrayLiteral: + Enabled: false + +Style/TrailingCommaInHashLiteral: Enabled: false Style/TrailingCommaInArguments: @@ -132,7 +135,7 @@ Style/ParallelAssignment: Layout/EmptyLineBetweenDefs: Enabled: false -Layout/FirstParameterIndentation: +Layout/IndentFirstArgument: Enabled: false Naming/ConstantName: @@ -150,6 +153,9 @@ Style/EmptyMethod: Style/FormatStringToken: Enabled: false +Style/FrozenStringLiteralComment: + Enabled: false + Style/GuardClause: Enabled: false @@ -162,7 +168,10 @@ Style/IfUnlessModifier: Style/IfUnlessModifierOfIfUnless: Enabled: false -Style/MethodMissing: +Style/MethodMissingSuper: + Enabled: false + +Style/MissingRespondToMissing: Enabled: false Style/MixinUsage: @@ -243,18 +252,13 @@ Style/StderrPuts: Style/TernaryParentheses: Enabled: false -# This could likely be enabled, but it had a false positive on rspec-mocks -# (suggested change was not behaviour preserving) so I don't trust it. -Performance/HashEachMethods: - Enabled: false - Naming/HeredocDelimiterNaming: Enabled: false Layout/EmptyLineAfterMagicComment: Enabled: false -Layout/IndentArray: +Layout/IndentFirstArrayElement: Enabled: false Layout/IndentAssignment: @@ -307,3 +311,6 @@ Style/TrailingUnderscoreVariable: Layout/EmptyLinesAroundAccessModifier: Enabled: false + +Metrics/AbcSize: + Enabled: false diff --git a/.travis.yml b/.travis.yml index a1cc8fe9ce..f3e3a596da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,180 +37,64 @@ dist: trusty matrix: include: - # Rails dev / 6 builds >= 2.4.4 + # Rails 6 builds + - rvm: jruby-head + jdk: oraclejdk11 + env: + - RAILS_VERSION='~>6.0' + - JRUBY_OPT=--dev + - JAVA_OPTS="--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED --add-opens=java.base/java.util.zip=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util.regex=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/javax.crypto=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED" - rvm: 2.6.3 - env: RAILS_VERSION=master - - rvm: 2.5.3 - env: RAILS_VERSION=master - - rvm: 2.4.4 - env: RAILS_VERSION=master + env: RAILS_VERSION='~>6.0' + - rvm: 2.5.5 + env: RAILS_VERSION='~>6.0' # Rails 5.2 builds >= 2.2.2 + - rvm: jruby-head + jdk: oraclejdk11 + env: + - RAILS_VERSION='~> 5.2.0' + - JRUBY_OPT=--dev + - JAVA_OPTS="--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.security.cert=ALL-UNNAMED --add-opens=java.base/java.util.zip=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util.regex=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/javax.crypto=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED" - rvm: 2.6.3 env: RAILS_VERSION='~> 5.2.0' - - rvm: 2.5.3 + - rvm: 2.5.5 env: RAILS_VERSION='~> 5.2.0' - - rvm: 2.4.4 + - rvm: 2.4.6 env: RAILS_VERSION='~> 5.2.0' - - rvm: 2.3.7 - env: RAILS_VERSION='~> 5.2.0' - - rvm: 2.2.10 + - rvm: 2.3.8 env: RAILS_VERSION='~> 5.2.0' # Rails 5.1 Builds >= 2.2.2 - rvm: 2.6.3 env: RAILS_VERSION='~> 5.1.0' - - rvm: 2.5.3 - env: RAILS_VERSION='~> 5.1.0' - - rvm: 2.4.4 + - rvm: 2.5.5 env: RAILS_VERSION='~> 5.1.0' - - rvm: 2.3.7 + - rvm: 2.4.6 env: RAILS_VERSION='~> 5.1.0' - - rvm: 2.2.10 + - rvm: 2.3.8 env: RAILS_VERSION='~> 5.1.0' # Rails 5.0 Builds >= 2.2.2 - rvm: 2.6.3 env: RAILS_VERSION='~> 5.0.0' - - rvm: 2.5.3 - env: RAILS_VERSION='~> 5.0.0' - - rvm: 2.4.4 + - rvm: 2.5.5 env: RAILS_VERSION='~> 5.0.0' - - rvm: 2.3.7 + - rvm: 2.4.6 env: RAILS_VERSION='~> 5.0.0' - - rvm: 2.2.10 + - rvm: 2.3.8 env: RAILS_VERSION='~> 5.0.0' # Rails 4.2 Builds >= 1.9.3 - - rvm: 2.4.4 + - rvm: 2.5.5 env: RAILS_VERSION='~> 4.2.0' - - rvm: 2.4.4 - env: RAILS_VERSION=4-2-stable - - rvm: 2.3.7 + - rvm: 2.4.6 env: RAILS_VERSION='~> 4.2.0' - - rvm: 2.3.7 - env: RAILS_VERSION=4-2-stable - - rvm: 2.2.10 + - rvm: 2.4.6 env: RAILS_VERSION='~> 4.2.0' - - rvm: 2.2.10 - env: RAILS_VERSION=4-2-stable - - rvm: 2.1.10 + - rvm: 2.3.8 env: RAILS_VERSION='~> 4.2.0' - - rvm: 2.1.10 - env: RAILS_VERSION=4-2-stable - - rvm: 2.0.0 + - rvm: 2.3.8 env: RAILS_VERSION='~> 4.2.0' - - rvm: 2.0.0 - env: RAILS_VERSION=4-2-stable - - rvm: 1.9.3 - env: RAILS_VERSION='~> 4.2.0' - - rvm: 1.9.3 - env: RAILS_VERSION=4-2-stable - - # Rails 4.1 Builds >= 1.9.3, < 2.4 - - rvm: 2.3.7 - env: RAILS_VERSION='~> 4.1.0' - - rvm: 2.3.7 - env: RAILS_VERSION=4-1-stable - - rvm: 2.2.10 - env: RAILS_VERSION='~> 4.1.0' - - rvm: 2.2.10 - env: RAILS_VERSION=4-1-stable - - rvm: 2.1.10 - env: RAILS_VERSION='~> 4.1.0' - - rvm: 2.1.10 - env: RAILS_VERSION=4-1-stable - - rvm: 2.0.0 - env: RAILS_VERSION='~> 4.1.0' - - rvm: 2.0.0 - env: RAILS_VERSION=4-1-stable - - rvm: 1.9.3 - env: RAILS_VERSION='~> 4.1.0' - - rvm: 1.9.3 - env: RAILS_VERSION=4-1-stable - - # Rails 4.0 Builds >= 1.8.11, < 2.4 - - rvm: 2.3.7 - env: RAILS_VERSION='~> 4.0.4' - - rvm: 2.3.7 - env: RAILS_VERSION=4-0-stable - - rvm: 2.2.10 - env: RAILS_VERSION='~> 4.0.4' - - rvm: 2.2.10 - env: RAILS_VERSION=4-0-stable - - rvm: 2.1.10 - env: RAILS_VERSION='~> 4.0.4' - - rvm: 2.1.10 - env: RAILS_VERSION=4-0-stable - - rvm: 2.0.0 - env: RAILS_VERSION='~> 4.0.4' - - rvm: 2.0.0 - env: RAILS_VERSION=4-0-stable - - rvm: 1.9.3 - env: RAILS_VERSION='~> 4.0.4' - - rvm: 1.9.3 - env: RAILS_VERSION=4-0-stable - # Rails 3.2 Builds < 2.4 - - rvm: 2.3.7 - env: RAILS_VERSION='~> 3.2.17' - - rvm: 2.3.7 - env: RAILS_VERSION=3-2-stable - - rvm: 2.2.10 - env: RAILS_VERSION='~> 3.2.17' - - rvm: 2.2.10 - env: RAILS_VERSION=3-2-stable - - rvm: 2.1.10 - env: RAILS_VERSION='~> 3.2.17' - - rvm: 2.1.10 - env: RAILS_VERSION=3-2-stable - - rvm: 2.0.0 - env: RAILS_VERSION='~> 3.2.17' - - rvm: 2.0.0 - env: RAILS_VERSION=3-2-stable - - rvm: 1.9.3 - env: RAILS_VERSION='~> 3.2.17' - - rvm: 1.9.3 - env: RAILS_VERSION=3-2-stable - - rvm: 1.9.2 - env: RAILS_VERSION='~> 3.2.17' - - rvm: 1.9.2 - env: RAILS_VERSION=3-2-stable - - rvm: 1.8.7 - env: RAILS_VERSION='~> 3.2.17' - - rvm: 1.8.7 - env: RAILS_VERSION=3-2-stable - - # Rails 3.1 Builds, < 2.2 - - rvm: 2.1.10 - env: RAILS_VERSION='~> 3.1.12' - - rvm: 2.0.0 - env: RAILS_VERSION='~> 3.1.12' - - rvm: 1.9.3 - env: RAILS_VERSION='~> 3.1.12' - - rvm: 1.9.2 - env: RAILS_VERSION='~> 3.1.12' - - rvm: 1.8.7 - env: RAILS_VERSION='~> 3.1.12' - - # Rails 3.0 Builds, < 2 - - rvm: 1.9.3 - env: RAILS_VERSION='~> 3.0.20' - - rvm: 1.9.2 - env: RAILS_VERSION='~> 3.0.20' - - rvm: 1.8.7 - env: RAILS_VERSION='~> 3.0.20' - - allow_failures: - - rvm: 2.6.3 - env: RAILS_VERSION=master - - rvm: 2.5.3 - env: RAILS_VERSION=master - - rvm: 2.4.4 - env: RAILS_VERSION=master fast_finish: true - -branches: - only: - - master - - /^\d+-\d+-maintenance$/ diff --git a/BUILD_DETAIL.md b/BUILD_DETAIL.md index 497a89db6f..6a048292e9 100644 --- a/BUILD_DETAIL.md +++ b/BUILD_DETAIL.md @@ -68,7 +68,7 @@ $ bin/cucumber ## YARD documentation -RSpec uses [YARD](https://yardoc.org/) for API documentation on the [rspec.info site](http://rspec.info/). +RSpec uses [YARD](https://yardoc.org/) for API documentation on the [rspec.info site](https://rspec.info/). Our commitment to [SemVer](https://semver.org) requires that we explicitly declare our public API, and our build uses YARD to ensure that every class, module and method has either been labeled `@private` or has at diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8be3fcec2..9f237508b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,3 +50,21 @@ immediately. These are good ones to tackle to help us actively fix bugs. Maintenance branches are how we manage the different supported point releases of RSpec. As such, while they might look like good candidates to merge into master, please do not open pull requests to merge them. + +## How do the cukes work? + +The cucumber features for RSpec rails document how it works, but are also quasi +executable tests for the framework. They execute in the context of a pre-setup +Rails app. + +1. Before the cucumber specs run, the directory `tmp/aruba` is cleared +2. If the example app hasn't already been created, + `bundle exec rake generate:app generate:stuff` is executed. +3. The example app is copied in to `tmp/aruba` +4. Everything in `tmp/aruba/spec/*` is deleted apart from `spec/spec_helper.rb` and + `spec/rails_helper.rb` +5. the cucumber suite executes, creating files in that app and executing them + +The best way to debug the app is to run a failing cucumber feature, which will +leave the test files intact in `tmp/aruba`, then you can cd in to that director +and run it in the bundle context of the aruba app. diff --git a/Changelog.md b/Changelog.md index f90af81ea9..1572d64d9e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,39 @@ ### Development [Full Changelog](https://github.com/rspec/rspec-rails/compare/v3.9.0...master) +Enhancements: + +* Adds support for Rails 6. (Penelope Phippen, Benoit Tigeot, Jon Rowe, #2071) +* Adds support for JRuby on Rails 5.2 and 6 +* Add support for parameterised mailers (Ignatius Reza, #2125) +* Add ActionMailbox spec helpers and test type (James Dabbs, #2119) +* Add ActionCable spec helpers and test type (Vladimir Dementyev, #2113) +* Add support for partial args when using `have_enqueued_mail` + (Ignatius Reza, #2118, #2125) +* Add support for time arguments for `have_enqueued_job` (@alpaca-tc, #2157) +* Improve path parsing in view specs render options. (John Hawthorn, #2115) +* Add routing spec template as an option for generating controller specs. + (David Revelo, #2134) + +Bug Fixes: + +* `EmptyTemplateHandler.call` now needs to support an additional argument in + Rails 6. (Pavel Rosický, #2089) +* Suppress warning from `SQLite3Adapter.represent_boolean_as_integer` which is + deprecated. (Pavel Rosický, #2092) +* `ActionView::Template#formats` has been deprecated and replaced by + `ActionView::Template#format`(Seb Jacobs, #2100) +* Replace `before_teardown` as well as `after_teardown` to ensure screenshots + are generated correctly. (Jon Rowe, #2164) +* `ActionView::FixtureResolver#hash` has been renamed to `ActionView::FixtureResolver#data`. + (Penelope Phippen, #2076) +* Add missing require for `have_enqueued_mail` matcher. (Ignatius Reza, #2117) + +Breaking Changes: + +* Drops support for Rails below 5.0 +* Drops support for Ruby below 2.3 + ### 3.9.0 / 2019-10-08 [Full Changelog](https://github.com/rspec/rspec-rails/compare/v3.8.3...v3.9.0) diff --git a/Gemfile b/Gemfile index 03852dce93..d4f616058a 100644 --- a/Gemfile +++ b/Gemfile @@ -11,84 +11,58 @@ gem 'yard', '~> 0.8.7', :require => false ### deps for rdoc.info group :documentation do - gem 'redcarpet', '2.3.0' - gem 'github-markup', '1.0.0' - if RUBY_VERSION > '2.0.0' - gem 'relish' - end + gem 'redcarpet', '~> 3.4.0', platforms: [:ruby] + gem 'github-markup', '~> 3.0.3' + gem 'relish', '~> 0.7.1' end platforms :jruby do gem "jruby-openssl" end -gem 'sqlite3', '~> 1.3.6' - -if RUBY_VERSION >= '2.4.0' - gem 'json', '>= 2.0.2' -end - -if RUBY_VERSION < '1.9' - gem 'ffi', '< 1.9.19' # ffi dropped Ruby 1.8 support in 1.9.19 +RAILS_VERSION ||= "" +match = /(\d+)(\.|-)(\d+)/.match(RAILS_VERSION) +if match.nil? + # will be nil if master + MAJOR = 6 + MINOR = 0 else - gem 'ffi', '~> 1.9.25' + MAJOR,MINOR = match.captures.map(&:to_i).compact end -if RUBY_VERSION >= '2.0.0' - gem 'rake', '>= 10.0.0' -elsif RUBY_VERSION >= '1.9.3' - gem 'rake', '< 12.0.0' # rake 12 requires Ruby 2.0.0 or later +if MAJOR >= 6 + gem 'sqlite3', '~> 1.4', platforms: [:ruby] + gem 'selenium-webdriver', '~> 3.5', :require => false else - gem 'rake', '< 11.0.0' # rake 11 requires Ruby 1.9.3 or later + gem 'sqlite3', '~> 1.3.6', platforms: [:ruby] end -# Version 3 of mime-types 3 requires Ruby 2.0 -if RUBY_VERSION < '2.0.0' - gem 'mime-types', '< 3' +if RUBY_VERSION >= '2.4.0' + gem 'json', '>= 2.0.2' end +gem 'ffi', '~> 1.9.25' + +gem 'rake', '~> 12' + +gem 'mime-types', "~> 3" + # Version 5.12 of minitest requires Ruby 2.4 if RUBY_VERSION < '2.4.0' gem 'minitest', '< 5.12.0' end -# Capybara versions that support RSpec 3 only support RUBY_VERSION >= 1.9.3 -if RUBY_VERSION >= '1.9.3' - if /5(\.|-)[1-9]\d*/ === RAILS_VERSION || "master" == RAILS_VERSION - gem 'capybara', '~> 2.13', :require => false - else - gem 'capybara', '~> 2.2.0', :require => false - end -end - -# Rack::Cache 1.3.0 requires Ruby >= 2.0.0 -gem 'rack-cache', '< 1.3.0' if RUBY_VERSION < '2.0.0' +gem 'capybara', '~> 2.13', :require => false -if RUBY_VERSION < '1.9.2' - gem 'nokogiri', '~> 1.5.0' -elsif RUBY_VERSION < '1.9.3' - gem 'nokogiri', '1.5.2' -elsif RUBY_VERSION < '2.1.0' - gem 'nokogiri', '1.6.8.1' -elsif RUBY_VERSION < '2.3.0' - gem 'nokogiri', '1.8.5' +if MAJOR == 6 + gem 'nokogiri' else - gem 'nokogiri', '~> 1.10' + gem 'nokogiri', '1.8.5' end -if RUBY_VERSION <= '1.8.7' - # cucumber and gherkin require rubyzip as a runtime dependency on 1.8.7 - # Only < 1.0 supports 1.8.7 - gem 'rubyzip', '< 1.0' -else - gem "rubyzip", '>= 1.2.2' -end +gem "rubyzip", '~> 1.2' -if RUBY_VERSION >= '2.0.0' && RUBY_VERSION < '2.2.0' - # our current rubocop version doesn't support the json version required by Ruby 2.4 - # our rails rubocop setup only supports 2.0 and 2.1 - gem 'rubocop', "~> 0.23.0" -end +gem 'rubocop', "~> 0.74" custom_gemfile = File.expand_path("../Gemfile-custom", __FILE__) eval_gemfile custom_gemfile if File.exist?(custom_gemfile) diff --git a/Gemfile-rails-dependencies b/Gemfile-rails-dependencies index a849eeeda4..9d35ffb896 100644 --- a/Gemfile-rails-dependencies +++ b/Gemfile-rails-dependencies @@ -7,43 +7,36 @@ when /master/ gem "activerecord-deprecated_finders", :git => "https://github.com/rails/activerecord-deprecated_finders.git" gem "rails-observers", :git => "https://github.com/rails/rails-observers" gem "web-console", :git => "https://github.com/rails/web-console", :group => :development - gem 'sass-rails', :git => "https://github.com/rails/sass-rails.git" gem 'coffee-rails', :git => "https://github.com/rails/coffee-rails.git" gem 'rack', :git => 'https://github.com/rack/rack.git' gem 'i18n', :git => 'https://github.com/svenfuchs/i18n.git', :branch => 'master' gem 'sprockets', :git => 'https://github.com/rails/sprockets.git', :branch => 'master' gem 'sprockets-rails', :git => 'https://github.com/rails/sprockets-rails.git', :branch => 'master' - if RUBY_VERSION >= "2.2" - gem 'puma', :git => 'https://github.com/puma/puma', :branch => 'master' - end + gem 'puma', "3.12.1" + gem 'activerecord-jdbcsqlite3-adapter', git: 'https://github.com/jruby/activerecord-jdbc-adapter', platforms: [:jruby] when /stable$/ gem_list = %w[rails railties actionmailer actionpack activerecord activesupport] - gem_list << 'activejob' if version > '4-1-stable' - gem_list << 'actionview' if version > '4-0-stable' - if RUBY_VERSION >= "2.2" - gem_list << 'puma' if version > '5-0-stable' - end + gem_list << 'activejob' if version >= '4-2-stable' + gem_list << 'actionview' if version >= '4-2-stable' + gem 'puma', "3.12.1" if version > '5-0-stable' + gem 'activerecord-jdbcsqlite3-adapter', git: 'https://github.com/jruby/activerecord-jdbc-adapter', platforms: [:jruby] gem_list.each do |rails_gem| gem rails_gem, :git => "https://github.com/rails/rails.git", :branch => version end when nil, false, "" - if RUBY_VERSION < '1.9.3' - # Rails 4+ requires 1.9.3+, so on earlier versions default to the last 3.x release. - gem "rails", "~> 3.2.17" - elsif RUBY_VERSION < '2.2.0' - # Rails 5+ requires 2.2+, so on earlier versions default to the last 4.x release. - gem "rails", "~> 4.2.0" - else - gem "rails", "~> 5.0.0" - end + gem "rails", "~> 5.0.0" + gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby] else gem "rails", version - if version >= '5-1-stable' && RUBY_VERSION >= "2.2" + if version >= '5-1-stable' && RUBY_VERSION >= "2.3" gem "puma" end -end -gem "i18n", '< 0.7.0' if RUBY_VERSION < '1.9.3' -gem "test-unit" if RUBY_VERSION >= '2.2.0' && version =~ /3[.-]2[.-]/ + if ENV['RAILS_VERSION'].gsub(/[^\d\.]/,'').to_f >= 6.0 + gem "activerecord-jdbcsqlite3-adapter", "~> 60.0.rc1", platforms: [:jruby] + else + gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby] + end +end diff --git a/Rakefile b/Rakefile index de4c5b3492..d7d4f3c2d0 100644 --- a/Rakefile +++ b/Rakefile @@ -28,6 +28,9 @@ end Cucumber::Rake::Task.new(:cucumber) do |t| version = ENV.fetch("RAILS_VERSION", "~> 4.2.0")[/\d[\.-]\d/] + if version == "master" || version.nil? + version = Float::INFINITY + end tags = [] if version.to_f >= 5.1 @@ -47,6 +50,14 @@ Cucumber::Rake::Task.new(:cucumber) do |t| tags << "~@system_test" end + if version.to_f >= 6.0 + tags << "~@rails_pre_6" + end + + if version.to_f < 6.0 + tags << "~@rails_post_6" + end + cucumber_flag = tags.map { |tag| "--tag #{tag}" } t.cucumber_opts = cucumber_flag @@ -60,7 +71,7 @@ namespace :generate do # Rails 4 cannot use a `rails` binstub generated by Bundler sh "rm -f #{bindir}/rails" - sh "bundle exec rails new ./tmp/example_app --no-rc --skip-javascript --skip-sprockets --skip-git --skip-test-unit --skip-listen --skip-bundle --template=example_app_generator/generate_app.rb" + sh "bundle exec rails new ./tmp/example_app --no-rc --skip-javascript --skip-bootsnap -skip-sprockets --skip-git --skip-test-unit --skip-listen --skip-bundle --template=example_app_generator/generate_app.rb" in_example_app do sh "./travis_retry_bundle_install.sh 2>&1" diff --git a/example_app_generator/config/initializers/sqlite3_fix.rb b/example_app_generator/config/initializers/sqlite3_fix.rb index 803ecf993e..b56cb12d73 100644 --- a/example_app_generator/config/initializers/sqlite3_fix.rb +++ b/example_app_generator/config/initializers/sqlite3_fix.rb @@ -1,3 +1,3 @@ -if Rails.application.config.respond_to?(:active_record) +if Rails.application.config.respond_to?(:active_record) && !(RUBY_ENGINE == "jruby") Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true end diff --git a/example_app_generator/generate_action_mailer_specs.rb b/example_app_generator/generate_action_mailer_specs.rb index 6997108a6d..fb2cc3b099 100644 --- a/example_app_generator/generate_action_mailer_specs.rb +++ b/example_app_generator/generate_action_mailer_specs.rb @@ -42,6 +42,7 @@ def comment_lines(path, flag, *args) Rails.application.class.parent.to_s if skip_active_record? comment_lines 'spec/support/default_preview_path', /active_record/ + comment_lines 'spec/support/default_preview_path', /active_storage/ end copy_file 'spec/verify_mailer_preview_path_spec.rb' end diff --git a/example_app_generator/generate_app.rb b/example_app_generator/generate_app.rb index c9b3c4c315..a407888601 100644 --- a/example_app_generator/generate_app.rb +++ b/example_app_generator/generate_app.rb @@ -1,4 +1,4 @@ -require 'nokogiri/version' +require 'nokogiri' rspec_rails_repo_path = File.expand_path("../../", __FILE__) rspec_dependencies_gemfile = File.join(rspec_rails_repo_path, 'Gemfile-rspec-dependencies') @@ -23,16 +23,25 @@ gsub_file "Gemfile", /.*debugger.*/, '' gsub_file "Gemfile", /.*byebug.*/, "gem 'byebug', '~> 9.0.6'" gsub_file "Gemfile", /.*puma.*/, "" - gsub_file "Gemfile", /.*sqlite3.*/, "gem 'sqlite3', '~> 1.3.6'" + gsub_file "Gemfile", /.*gem..sqlite3.*/, "gem 'sqlite3', '~> 1.3.6'" + gsub_file "Gemfile", /.*bootsnap.*/, "" if RUBY_VERSION < '2.2.2' gsub_file "Gemfile", /.*rdoc.*/, "gem 'rdoc', '< 6'" end if Rails::VERSION::STRING >= '5.0.0' - append_to_file('Gemfile', "gem 'rails-controller-testing', :git => 'https://github.com/rails/rails-controller-testing'\n") + append_to_file('Gemfile', "gem 'rails-controller-testing'\n") + end + + if Rails::VERSION::STRING >= '6' + gsub_file "Gemfile", /.*gem..sqlite3.*/, "gem 'sqlite3', '~> 1.4'" + gsub_file "Gemfile", /.*rails-controller-testing.*/, "gem 'rails-controller-testing', git: 'https://github.com/rails/rails-controller-testing'" end if Rails::VERSION::STRING >= "5.1.0" + if RUBY_VERSION < "2.4" + gsub_file "Gemfile", /.*capybara.*/, "gem 'capybara', '~> 3.15.0'" + end if Rails::VERSION::STRING >= "5.2.0" && RUBY_VERSION < '2.3.0' gsub_file "Gemfile", /.*chromedriver-helper.*/, "gem 'webdrivers', '< 4.0.0'" else @@ -46,7 +55,11 @@ # Nokogiri version is pinned in rspec-rails' Gemfile since it tend to cause installation problems # on Travis CI, so we pin nokogiri in this example app also. - append_to_file 'Gemfile', "gem 'nokogiri', '#{Nokogiri::VERSION}'\n" + if RUBY_ENGINE != "jruby" + append_to_file 'Gemfile', "gem 'nokogiri', '#{Nokogiri::VERSION}'\n" + else + gsub_file "Gemfile", /.*jdbc.*/, "" + end # Use our version of RSpec and Rails append_to_file 'Gemfile', <<-EOT.gsub(/^ +\|/, '') diff --git a/example_app_generator/generate_stuff.rb b/example_app_generator/generate_stuff.rb index 725c7f7530..3bfac428a4 100644 --- a/example_app_generator/generate_stuff.rb +++ b/example_app_generator/generate_stuff.rb @@ -111,6 +111,13 @@ def using_source_path(path) rescue LoadError end +begin + require 'action_cable' + require 'action_cable/test_helper' + generate('channel chat') +rescue LoadError +end + file "app/views/things/custom_action.html.erb", "This is a template for a custom action.", :force => true diff --git a/example_app_generator/spec/verify_custom_renderers_spec.rb b/example_app_generator/spec/verify_custom_renderers_spec.rb index cc3ecde266..6665eb7ec0 100644 --- a/example_app_generator/spec/verify_custom_renderers_spec.rb +++ b/example_app_generator/spec/verify_custom_renderers_spec.rb @@ -37,7 +37,7 @@ def index expect(response).to render_template(:bar) end - it "renders an empty string" do + it "renders an empty string", :skip => Rails::VERSION::STRING.to_f >= 6.0 do get :index expect(response.body).to eq("") @@ -150,9 +150,10 @@ def find_template(name, path) ActionView::Template.new( "", name, - lambda { |_template| %("Dynamic template with path '#{_template.virtual_path}'") }, + lambda { |_template, _source = nil| %("Dynamic template with path '#{_template.virtual_path}'") }, :virtual_path => path, - :format => :html + :format => :html, + :locals => [] ) end end diff --git a/example_app_generator/spec/verify_mailer_preview_path_spec.rb b/example_app_generator/spec/verify_mailer_preview_path_spec.rb index a2b48b01c0..2b27522206 100644 --- a/example_app_generator/spec/verify_mailer_preview_path_spec.rb +++ b/example_app_generator/spec/verify_mailer_preview_path_spec.rb @@ -44,6 +44,7 @@ def have_no_preview let(:rails_env) { 'development' } it 'sets the preview path to the default rspec path' do + skip "this spec fails singularly on JRuby due to weird env things" if RUBY_ENGINE == "jruby" expect(capture_exec(custom_env, exec_script)).to eq( "#{::Rails.root}/spec/mailers/previews" ) diff --git a/features/backtrace_filtering.feature b/features/backtrace_filtering.feature index 5067ddce52..f4e5ddac39 100644 --- a/features/backtrace_filtering.feature +++ b/features/backtrace_filtering.feature @@ -1,6 +1,7 @@ Feature: backtrace filtering - The following configuration setting will filter out lines in backtraces that come from Rails gems in order to reduce the noise in test failure output: + The following configuration setting will filter out lines in backtraces + that come from Rails gems in order to reduce the noise in test failure output: ```ruby RSpec.configure do |config| @@ -8,7 +9,8 @@ Feature: backtrace filtering end ``` - `rspec` will always show the full backtrace output when run with the `--backtrace` commandline option. + `rspec` will always show the full backtrace output when run with + the `--backtrace` commandline option. Background: Using `filter_rails_from_backtrace!` Given a file named "spec/failing_spec.rb" with: diff --git a/features/channel_specs/channel_spec.feature b/features/channel_specs/channel_spec.feature new file mode 100644 index 0000000000..3c48fdd7b2 --- /dev/null +++ b/features/channel_specs/channel_spec.feature @@ -0,0 +1,216 @@ +@rails_post_6 +Feature: channel spec + + Channel specs are marked by `:type => :channel` or if you have set + `config.infer_spec_type_from_file_location!` by placing them in `spec/channels`. + + A channel spec is a thin wrapper for an `ActionCable::Channel::TestCase`, and includes all + of the behavior and assertions that it provides, in addition to RSpec's own + behavior and expectations. + + It also includes helpers from `ActionCable::Connection::TestCase` to make it possible to + test connection behavior. + + Background: + Given action cable testing is available + And a file named "app/channels/chat_channel.rb" with: + """ruby + class ChatChannel < ApplicationCable::Channel + def subscribed + reject unless params[:room_id].present? + end + + def speak(data) + ActionCable.server.broadcast( + "chat_#{params[:room_id]}", text: data['message'] + ) + end + + def echo(data) + data.delete("action") + transmit data + end + end + """ + + Scenario: simple passing example + Given a file named "spec/channels/chat_channel_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ChatChannel, :type => :channel do + it "successfully subscribes" do + subscribe room_id: 42 + expect(subscription).to be_confirmed + end + end + """ + When I run `rspec spec/channels/chat_channel_spec.rb` + Then the example should pass + + Scenario: verifying that subscription is rejected + Given a file named "spec/channels/chat_channel_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ChatChannel, :type => :channel do + it "rejects subscription" do + subscribe room_id: nil + expect(subscription).to be_rejected + end + end + """ + When I run `rspec spec/channels/chat_channel_spec.rb` + Then the example should pass + + Scenario: performing actions and checking transmissions + Given a file named "spec/channels/chat_channel_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ChatChannel, :type => :channel do + it "successfully subscribes" do + subscribe room_id: 42 + + perform :echo, foo: 'bar' + expect(transmissions.last).to eq('foo' => 'bar') + end + end + """ + When I run `rspec spec/channels/chat_channel_spec.rb` + Then the example should pass + + Scenario: successful connection with url params + Given a file named "app/channels/application_cable/connection.rb" with: + """ruby + class ApplicationCable::Connection < ActionCable::Connection::Base + identified_by :user_id + + def connect + self.user_id = request.params[:user_id] + reject_unauthorized_connection unless user_id.present? + end + end + """ + And a file named "spec/channels/connection_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ApplicationCable::Connection, :type => :channel do + it "successfully connects" do + connect "/cable?user_id=323" + expect(connection.user_id).to eq "323" + end + end + """ + When I run `rspec spec/channels/connection_spec.rb` + Then the example should pass + + Scenario: successful connection with cookies + Given a file named "app/channels/application_cable/connection.rb" with: + """ruby + class ApplicationCable::Connection < ActionCable::Connection::Base + identified_by :user_id + + def connect + self.user_id = cookies.signed[:user_id] + reject_unauthorized_connection unless user_id.present? + end + end + """ + And a file named "spec/channels/connection_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ApplicationCable::Connection, :type => :channel do + it "successfully connects" do + cookies.signed[:user_id] = "324" + + connect "/cable" + expect(connection.user_id).to eq "324" + end + end + """ + When I run `rspec spec/channels/connection_spec.rb` + Then the example should pass + + Scenario: successful connection with headers + Given a file named "app/channels/application_cable/connection.rb" with: + """ruby + class ApplicationCable::Connection < ActionCable::Connection::Base + identified_by :user_id + + def connect + self.user_id = request.headers["x-user-id"] + reject_unauthorized_connection unless user_id.present? + end + end + """ + And a file named "spec/channels/connection_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ApplicationCable::Connection, :type => :channel do + it "successfully connects" do + connect "/cable", headers: { "X-USER-ID" => "325" } + expect(connection.user_id).to eq "325" + end + end + """ + When I run `rspec spec/channels/connection_spec.rb` + Then the example should pass + + Scenario: rejected connection + Given a file named "app/channels/application_cable/connection.rb" with: + """ruby + class ApplicationCable::Connection < ActionCable::Connection::Base + identified_by :user_id + + def connect + self.user_id = request.params[:user_id] + reject_unauthorized_connection unless user_id.present? + end + end + """ + And a file named "spec/channels/connection_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ApplicationCable::Connection, :type => :channel do + it "rejects connection" do + expect { connect "/cable?user_id=" }.to have_rejected_connection + end + end + """ + When I run `rspec spec/channels/connection_spec.rb` + Then the example should pass + + Scenario: disconnect connection + Given a file named "app/channels/application_cable/connection.rb" with: + """ruby + class ApplicationCable::Connection < ActionCable::Connection::Base + identified_by :user_id + + def connect + self.user_id = request.params[:user_id] + reject_unauthorized_connection unless user_id.present? + end + + def disconnect + $stdout.puts "User #{user_id} disconnected" + end + end + """ + And a file named "spec/channels/connection_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ApplicationCable::Connection, :type => :channel do + it "disconnects" do + connect "/cable?user_id=42" + expect { disconnect }.to output(/User 42 disconnected/).to_stdout + end + end + """ + When I run `rspec spec/channels/connection_spec.rb` + Then the example should pass diff --git a/features/controller_specs/Cookies.md b/features/controller_specs/Cookies.md deleted file mode 100644 index 100c8366d2..0000000000 --- a/features/controller_specs/Cookies.md +++ /dev/null @@ -1,56 +0,0 @@ -Controller specs wrap Rails controller tests, which expose a few different ways -to access cookies: - - @request.cookies['key'] - @response.cookies['key'] - cookies['key'] - -rails-3.0.x and 3.1 handle these slightly differently, so to avoid confusion, we recommend -the following guidelines: - -### Recommended guidelines for rails-3.0.0 to 3.1.0 - - * Access cookies through the `request` and `response` objects in the spec. - * Use `request.cookies` before the action to set up state. - * Use `response.cookies` after the action to specify outcomes. - * Use the `cookies` object in the controller action. - * Use String keys. - -
-# spec
-request.cookies['foo'] = 'bar'
-get :some_action
-expect(response.cookies['foo']).to eq('modified bar')
-
-# controller
-def some_action
-  cookies['foo'] = "modified #{cookies['foo']}"
-end
-
- -#### Why use Strings instead of Symbols? - -The `cookies` objects in the spec come from Rack, and do not support -indifferent access (i.e. `:foo` and `"foo"` are different keys). The `cookies` -object in the controller _does_ support indifferent access, which is a bit -confusing. - -This changed in rails-3.1, so you _can_ use symbol keys, but we recommend -sticking with string keys for consistency. - -#### Why not use the `cookies` method? - -The `cookies` method combines the `request` and `response` cookies. This can -lead to confusion when setting cookies in the example in order to set up state -for the controller action. - - # does not work in rails 3.0.0 > 3.1.0 - cookies['foo'] = 'bar' # this is not visible in the controller - get :some_action - -### Future versions of Rails - -There is code in the master branch in rails that makes cookie access more -consistent so you can use the same `cookies` object before and after the action, -and you can use String or Symbol keys. We'll update these docs accordingly when -that is released. diff --git a/features/controller_specs/controller_spec.feature b/features/controller_specs/controller_spec.feature index 52c2908dc1..d4c8e41dfb 100644 --- a/features/controller_specs/controller_spec.feature +++ b/features/controller_specs/controller_spec.feature @@ -57,7 +57,7 @@ Feature: controller spec When I run `rspec spec` Then the example should pass - @rails_post_5 + @rails_post_5 @rails_pre_6 Scenario: setting a different content type for example json (request type) Given a file named "spec/controllers/widgets_controller_spec.rb" with: """ruby @@ -79,3 +79,49 @@ Feature: controller spec """ When I run `rspec spec` Then the example should pass + + @rails_post_6 + Scenario: setting a different content type for example json (request type) + Given a file named "spec/controllers/widgets_controller_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe WidgetsController, :type => :controller do + describe "responds to" do + it "responds to html by default" do + post :create, :params => { :widget => { :name => "Any Name" } } + expect(response.content_type).to eq "text/html; charset=utf-8" + end + + it "responds to custom formats when provided in the params" do + post :create, :params => { :widget => { :name => "Any Name" }, :format => :json } + expect(response.content_type).to eq "application/json; charset=utf-8" + end + end + end + """ + When I run `rspec spec` + Then the example should pass + + @rails_post_6 + Scenario: setting a different media type for example json (request type) + Given a file named "spec/controllers/widgets_controller_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe WidgetsController, :type => :controller do + describe "responds to" do + it "responds to html by default" do + post :create, :params => { :widget => { :name => "Any Name" } } + expect(response.media_type).to eq "text/html" + end + + it "responds to custom formats when provided in the params" do + post :create, :params => { :widget => { :name => "Any Name" }, :format => :json } + expect(response.media_type).to eq "application/json" + end + end + end + """ + When I run `rspec spec` + Then the example should pass diff --git a/features/controller_specs/cookies.feature b/features/controller_specs/cookies.feature new file mode 100644 index 0000000000..a242e6cd2d --- /dev/null +++ b/features/controller_specs/cookies.feature @@ -0,0 +1,36 @@ +Feature: Cookies + + There are different ways to make assertions on cookies from controller specs, + but we recommend using the `cookies` method as set out below. + + You can use strings or symbols to fetch or set your cookies because the `cookies` + method supports indifferent access. + + Scenario: Testing cookie's value cleared in controller + Given a file named "spec/controllers/application_controller_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ApplicationController, :type => :controller do + controller do + def clear_cookie + cookies.delete(:user_name) + head :ok + end + end + + before do + routes.draw { get "clear_cookie" => "anonymous#clear_cookie" } + end + + it "clear cookie's value 'user_name'" do + cookies[:user_name] = "Sam" + + get :clear_cookie + + expect(cookies[:user_name]).to eq nil + end + end + """ + When I run `rspec spec` + Then the example should pass diff --git a/features/controller_specs/isolation_from_views.feature b/features/controller_specs/isolation_from_views.feature index 2aab00b901..2a5971d832 100644 --- a/features/controller_specs/isolation_from_views.feature +++ b/features/controller_specs/isolation_from_views.feature @@ -64,6 +64,17 @@ Feature: views are stubbed by default end end """ + Given a file named "app/controllers/things_controller.rb" with: + """ruby + class ThingsController < ActionController::Base + layout false + def custom_action + end + end + """ + Given a file named "app/views/things/custom_action.html.erb" with: + """ + """ When I run `rspec spec` Then the examples should all pass diff --git a/features/matchers/have_broadcasted_matcher.feature b/features/matchers/have_broadcasted_matcher.feature new file mode 100644 index 0000000000..f83fd6af20 --- /dev/null +++ b/features/matchers/have_broadcasted_matcher.feature @@ -0,0 +1,152 @@ +@rails_post_6 +Feature: have_broadcasted matcher + + The `have_broadcasted_to` (also aliased as `broadcast_to`) matcher is used + to check if a message has been broadcasted to a given stream. + + Background: + Given action cable testing is available + + Scenario: Checking stream name + Given a file named "spec/models/broadcaster_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe "broadcasting" do + it "matches with stream name" do + expect { + ActionCable.server.broadcast( + "notifications", text: 'Hello!' + ) + }.to have_broadcasted_to("notifications") + end + end + """ + When I run `rspec spec/models/broadcaster_spec.rb` + Then the examples should all pass + + Scenario: Checking passed message to stream + Given a file named "spec/models/broadcaster_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe "broadcasting" do + it "matches with message" do + expect { + ActionCable.server.broadcast( + "notifications", text: 'Hello!' + ) + }.to have_broadcasted_to("notifications").with(text: 'Hello!') + end + end + """ + When I run `rspec spec/models/broadcaster_spec.rb` + Then the examples should all pass + + Scenario: Checking that message passed to stream matches + Given a file named "spec/models/broadcaster_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe "broadcasting" do + it "matches with message" do + expect { + ActionCable.server.broadcast( + "notifications", text: 'Hello!', user_id: 12 + ) + }.to have_broadcasted_to("notifications").with(a_hash_including(text: 'Hello!')) + end + end + """ + When I run `rspec spec/models/broadcaster_spec.rb` + Then the examples should all pass + + Scenario: Checking passed message with block + Given a file named "spec/models/broadcaster_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe "broadcasting" do + it "matches with message" do + expect { + ActionCable.server.broadcast( + "notifications", text: 'Hello!', user_id: 12 + ) + }.to have_broadcasted_to("notifications").with { |data| + expect(data['user_id']).to eq 12 + } + end + end + """ + When I run `rspec spec/models/broadcaster_spec.rb` + Then the examples should all pass + + Scenario: Using alias method + Given a file named "spec/models/broadcaster_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe "broadcasting" do + it "matches with stream name" do + expect { + ActionCable.server.broadcast( + "notifications", text: 'Hello!' + ) + }.to broadcast_to("notifications") + end + end + """ + When I run `rspec spec/models/broadcaster_spec.rb` + Then the examples should all pass + + Scenario: Checking broadcast to a record + Given a file named "spec/channels/chat_channel_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ChatChannel, :type => :channel do + it "successfully subscribes" do + user = User.new(42) + + expect { + ChatChannel.broadcast_to(user, text: 'Hi') + }.to have_broadcasted_to(user) + end + end + """ + And a file named "app/models/user.rb" with: + """ruby + class User < Struct.new(:name) + def to_gid_param + name + end + end + """ + When I run `rspec spec/channels/chat_channel_spec.rb` + Then the example should pass + + Scenario: Checking broadcast to a record in non-channel spec + Given a file named "spec/models/broadcaster_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe "broadcasting" do + it "matches with stream name" do + user = User.new(42) + + expect { + ChatChannel.broadcast_to(user, text: 'Hi') + }.to broadcast_to(ChatChannel.broadcasting_for(user)) + end + end + """ + And a file named "app/models/user.rb" with: + """ruby + class User < Struct.new(:name) + def to_gid_param + name + end + end + """ + When I run `rspec spec/models/broadcaster_spec.rb` + Then the example should pass diff --git a/features/matchers/have_enqueued_mail_matcher.feature b/features/matchers/have_enqueued_mail_matcher.feature new file mode 100644 index 0000000000..e1ebbf886e --- /dev/null +++ b/features/matchers/have_enqueued_mail_matcher.feature @@ -0,0 +1,78 @@ +Feature: have_enqueued_mail matcher + + The `have_enqueued_mail` (also aliased as `enqueue_mail`) matcher is used to check if given mailer was enqueued. + + Background: + Given active job is available + + @rails_post_5 + Scenario: Checking mailer class and method name + Given a file named "spec/mailers/user_mailer_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe NotificationsMailer do + it "matches with enqueued mailer" do + ActiveJob::Base.queue_adapter = :test + expect { + NotificationsMailer.signup.deliver_later + }.to have_enqueued_mail(NotificationsMailer, :signup) + end + end + """ + When I run `rspec spec/mailers/user_mailer_spec.rb` + Then the examples should all pass + + @rails_post_5 + Scenario: Checking mailer enqueued time + Given a file named "spec/mailers/user_mailer_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe NotificationsMailer do + it "matches with enqueued mailer" do + ActiveJob::Base.queue_adapter = :test + expect { + NotificationsMailer.signup.deliver_later(:wait_until => Date.tomorrow.noon) + }.to have_enqueued_mail.at(Date.tomorrow.noon) + end + end + """ + When I run `rspec spec/mailers/user_mailer_spec.rb` + Then the examples should all pass + + @rails_pre_5 + Scenario: Checking mailer class and method name + Given a file named "spec/mailers/user_mailer_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe Notifications do + it "matches with enqueued mailer" do + ActiveJob::Base.queue_adapter = :test + expect { + Notifications.signup.deliver_later + }.to have_enqueued_mail(Notifications, :signup) + end + end + """ + When I run `rspec spec/mailers/user_mailer_spec.rb` + Then the examples should all pass + + @rails_pre_5 + Scenario: Checking mailer enqueued time + Given a file named "spec/mailers/user_mailer_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe Notifications do + it "matches with enqueued mailer" do + ActiveJob::Base.queue_adapter = :test + expect { + Notifications.signup.deliver_later(:wait_until => Date.tomorrow.noon) + }.to have_enqueued_mail.at(Date.tomorrow.noon) + end + end + """ + When I run `rspec spec/mailers/user_mailer_spec.rb` + Then the examples should all pass diff --git a/features/matchers/have_stream_from_matcher.feature b/features/matchers/have_stream_from_matcher.feature new file mode 100644 index 0000000000..9de55cfcf9 --- /dev/null +++ b/features/matchers/have_stream_from_matcher.feature @@ -0,0 +1,101 @@ +@rails_post_6 +Feature: have_stream_from matcher + + The `have_stream_from` matcher is used to check if a channel has been subscribed to a given stream specified as a String. + If you use `stream_for` in you channel to subscribe to a model, use `have_stream_for` matcher instead. + + The `have_no_streams` matcher is used to check if a channe hasn't been subscribed to any stream. + + It is available only in channel specs. + + Background: + Given action cable testing is available + + And a file named "app/channels/chat_channel.rb" with: + """ruby + class ChatChannel < ApplicationCable::Channel + def subscribed + reject unless params[:room_id].present? + + stream_from "chat_#{params[:room_id]}" + end + + def leave + stop_all_streams + end + end + """ + + Scenario: subscribing with params and checking streams + Given a file named "spec/channels/chat_channel_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ChatChannel, :type => :channel do + it "successfully subscribes" do + subscribe room_id: 42 + + expect(subscription).to be_confirmed + expect(subscription).to have_stream_from("chat_42") + end + end + """ + When I run `rspec spec/channels/chat_channel_spec.rb` + Then the example should pass + + Scenario: stopping all streams + Given a file named "spec/channels/chat_channel_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe ChatChannel, :type => :channel do + it "successfully subscribes" do + subscribe(room_id: 42) + + expect(subscription).to have_stream_from("chat_42") + + perform :leave + expect(subscription).not_to have_streams + end + end + """ + When I run `rspec spec/channels/chat_channel_spec.rb` + Then the example should pass + + Scenario: subscribing and checking streams for models + Given a file named "app/channels/notifications_channel.rb" with: + """ruby + class NotificationsChannel < ApplicationCable::Channel + def subscribed + stream_for current_user + end + end + """ + And a file named "app/channels/application_cable/connection.rb" with: + """ruby + class ApplicationCable::Connection < ActionCable::Connection::Base + identified_by :current_user + end + """ + And a file named "app/models/user.rb" with: + """ruby + class User < Struct.new(:name) + def to_gid_param + name + end + end + """ + And a file named "spec/channels/user_channel_spec.rb" with: + """ruby + require "rails_helper" + RSpec.describe NotificationsChannel, :type => :channel do + it "successfully subscribes to user's stream" do + stub_connection current_user: User.new(42) + subscribe + expect(subscription).to be_confirmed + expect(subscription).to have_stream_for(User.new(42)) + end + end + """ + When I run `rspec spec/channels/user_channel_spec.rb` + Then the example should pass diff --git a/features/request_specs/request_spec.feature b/features/request_specs/request_spec.feature index 459132f6a1..085a94fb4a 100644 --- a/features/request_specs/request_spec.feature +++ b/features/request_specs/request_spec.feature @@ -110,7 +110,7 @@ Feature: request spec When I run `rspec spec/requests/widget_management_spec.rb` Then the example should pass - @rails_post_5 + @rails_post_5 @rails_pre_6 Scenario: requesting a JSON response Given a file named "spec/requests/widget_management_spec.rb" with: """ruby @@ -119,10 +119,7 @@ Feature: request spec RSpec.describe "Widget management", :type => :request do it "creates a Widget" do - headers = { - "ACCEPT" => "application/json", # This is what Rails 4 accepts - "HTTP_ACCEPT" => "application/json" # This is what Rails 3 accepts - } + headers = { "ACCEPT" => "application/json" } post "/widgets", :params => { :widget => {:name => "My Widget"} }, :headers => headers expect(response.content_type).to eq("application/json") @@ -134,6 +131,25 @@ Feature: request spec When I run `rspec spec/requests/widget_management_spec.rb` Then the example should pass + @rails_post_6 + Scenario: requesting a JSON response + Given a file named "spec/requests/widget_management_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe "Widget management", :type => :request do + it "creates a Widget" do + headers = { "ACCEPT" => "application/json" } + post "/widgets", :params => { :widget => {:name => "My Widget"} }, :headers => headers + + expect(response.content_type).to eq("application/json; charset=utf-8") + expect(response).to have_http_status(:created) + end + end + """ + When I run `rspec spec/requests/widget_management_spec.rb` + Then the example should pass + @rails_pre_5 Scenario: providing JSON data Given a file named "spec/requests/widget_management_spec.rb" with: diff --git a/features/step_definitions/additional_cli_steps.rb b/features/step_definitions/additional_cli_steps.rb index 5f024f4aa4..622ddbec33 100644 --- a/features/step_definitions/additional_cli_steps.rb +++ b/features/step_definitions/additional_cli_steps.rb @@ -2,6 +2,11 @@ require "active_job" rescue LoadError # rubocop:disable Lint/HandleExceptions end +begin + require "action_cable" +rescue LoadError # rubocop:disable Lint/HandleExceptions +end + require "rails/version" require "rspec/rails/feature_check" @@ -27,3 +32,9 @@ pending "file fixtures are not available" end end + +Given /action cable testing is available/ do + if !RSpec::Rails::FeatureCheck.has_action_cable_testing? + pending "Action Cable testing is not available" + end +end diff --git a/lib/generators/rspec/channel/channel_generator.rb b/lib/generators/rspec/channel/channel_generator.rb new file mode 100644 index 0000000000..1e1937bc99 --- /dev/null +++ b/lib/generators/rspec/channel/channel_generator.rb @@ -0,0 +1,12 @@ +require 'generators/rspec' + +module Rspec + module Generators + # @private + class ChannelGenerator < Base + def create_channel_spec + template 'channel_spec.rb.erb', File.join('spec/channels', class_path, "#{file_name}_channel_spec.rb") + end + end + end +end diff --git a/lib/generators/rspec/channel/templates/channel_spec.rb.erb b/lib/generators/rspec/channel/templates/channel_spec.rb.erb new file mode 100644 index 0000000000..295377effb --- /dev/null +++ b/lib/generators/rspec/channel/templates/channel_spec.rb.erb @@ -0,0 +1,7 @@ +require 'rails_helper' + +<% module_namespacing do -%> +RSpec.describe <%= class_name %>Channel, <%= type_metatag(:channel) %> do + pending "add some examples to (or delete) #{__FILE__}" +end +<% end -%> diff --git a/lib/generators/rspec/controller/controller_generator.rb b/lib/generators/rspec/controller/controller_generator.rb index 40a07d56ce..dc3c23d37b 100644 --- a/lib/generators/rspec/controller/controller_generator.rb +++ b/lib/generators/rspec/controller/controller_generator.rb @@ -7,8 +7,9 @@ class ControllerGenerator < Base argument :actions, :type => :array, :default => [], :banner => "action action" class_option :template_engine, :desc => "Template engine to generate view files" - class_option :controller_specs, :type => :boolean, :default => true - class_option :view_specs, :type => :boolean, :default => true + class_option :controller_specs, :type => :boolean, :default => true, :desc => "Generate controller specs" + class_option :view_specs, :type => :boolean, :default => true, :desc => "Generate view specs" + class_option :routing_specs, :type => :boolean, :default => false, :desc => "Generate routing specs" def generate_controller_spec return unless options[:controller_specs] @@ -29,6 +30,14 @@ def generate_view_specs File.join("spec", "views", file_path, "#{@action}.html.#{options[:template_engine]}_spec.rb") end end + + def generate_routing_spec + return if actions.empty? + return unless options[:routing_specs] + + template 'routing_spec.rb', + File.join('spec/routing', class_path, "#{file_name}_routing_spec.rb") + end end end end diff --git a/lib/generators/rspec/controller/templates/routing_spec.rb b/lib/generators/rspec/controller/templates/routing_spec.rb new file mode 100644 index 0000000000..08550dd20a --- /dev/null +++ b/lib/generators/rspec/controller/templates/routing_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +<% module_namespacing do -%> +RSpec.describe '<%= class_name %>Controller', <%= type_metatag(:routing) %> do + describe 'routing' do +<% for action in actions -%> + it 'routes to #<%= action %>' do + expect(:get => "/<%= class_name.underscore %>/<%= action %>").to route_to("<%= class_name.underscore %>#<%= action %>") + end +<% end -%> + end +end +<% end -%> diff --git a/lib/generators/rspec/mailbox/mailbox_generator.rb b/lib/generators/rspec/mailbox/mailbox_generator.rb new file mode 100644 index 0000000000..1277326e3b --- /dev/null +++ b/lib/generators/rspec/mailbox/mailbox_generator.rb @@ -0,0 +1,14 @@ +require 'generators/rspec' + +module Rspec + module Generators + # @private + class MailboxGenerator < Base + def create_mailbox_spec + template('mailbox_spec.rb.erb', + File.join('spec/mailboxes', class_path, "#{file_name}_mailbox_spec.rb") + ) + end + end + end +end diff --git a/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb b/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb new file mode 100644 index 0000000000..3766bd3915 --- /dev/null +++ b/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb @@ -0,0 +1,7 @@ +require 'rails_helper' + +<% module_namespacing do -%> +RSpec.describe <%= class_name %>Mailbox, <%= type_metatag(:mailbox) %> do + pending "add some examples to (or delete) #{__FILE__}" +end +<% end -%> diff --git a/lib/generators/rspec/mailer/mailer_generator.rb b/lib/generators/rspec/mailer/mailer_generator.rb index 4fc31c39f2..768dfe8d45 100644 --- a/lib/generators/rspec/mailer/mailer_generator.rb +++ b/lib/generators/rspec/mailer/mailer_generator.rb @@ -20,6 +20,7 @@ def generate_fixtures_files def generate_preview_files return unless RSpec::Rails::FeatureCheck.has_action_mailer_preview? + template "preview.rb", File.join("spec/mailers/previews", class_path, "#{file_name}_preview.rb") end end diff --git a/lib/generators/rspec/model/model_generator.rb b/lib/generators/rspec/model/model_generator.rb index 789fcda83c..fcbaaecb52 100644 --- a/lib/generators/rspec/model/model_generator.rb +++ b/lib/generators/rspec/model/model_generator.rb @@ -23,6 +23,7 @@ def create_model_spec def create_fixture_file return unless missing_fixture_replacement? + template 'fixtures.yml', File.join('spec/fixtures', class_path, "#{(pluralize_table_names? ? plural_file_name : file_name)}.yml") end diff --git a/lib/generators/rspec/request/request_generator.rb b/lib/generators/rspec/request/request_generator.rb index 5f47c62cb5..b9e15f3ada 100644 --- a/lib/generators/rspec/request/request_generator.rb +++ b/lib/generators/rspec/request/request_generator.rb @@ -4,7 +4,7 @@ module Rspec module Generators # @private class RequestGenerator < IntegrationGenerator - source_paths << File.expand_path("../../integration/templates", __FILE__) + source_paths << File.expand_path('../integration/templates', __dir__) end end end diff --git a/lib/generators/rspec/scaffold/scaffold_generator.rb b/lib/generators/rspec/scaffold/scaffold_generator.rb index 8510e833ed..67cf0c85a7 100644 --- a/lib/generators/rspec/scaffold/scaffold_generator.rb +++ b/lib/generators/rspec/scaffold/scaffold_generator.rb @@ -6,7 +6,7 @@ module Generators # @private class ScaffoldGenerator < Base include ::Rails::Generators::ResourceHelpers - source_paths << File.expand_path("../../helper/templates", __FILE__) + source_paths << File.expand_path('../helper/templates', __dir__) argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" class_option :orm, :desc => "ORM used to generate the controller" @@ -74,12 +74,14 @@ def copy_view(view) # support for namespaced-resources def ns_file_name return file_name if ns_parts.empty? + "#{ns_prefix.map(&:underscore).join('/')}_#{ns_suffix.singularize.underscore}" end # support for namespaced-resources def ns_table_name return table_name if ns_parts.empty? + "#{ns_prefix.map(&:underscore).join('/')}/#{ns_suffix.tableize}" end diff --git a/lib/rspec-rails.rb b/lib/rspec-rails.rb index 9a9be782c6..3d796df405 100644 --- a/lib/rspec-rails.rb +++ b/lib/rspec-rails.rb @@ -39,6 +39,7 @@ class Railtie < ::Rails::Railtie def setup_preview_path(app) return unless supports_action_mailer_previews?(app.config) + options = app.config.action_mailer config_default_preview_path(options) if config_preview_path?(options) end @@ -57,6 +58,7 @@ def config_preview_path?(options) def config_default_preview_path(options) return unless options.preview_path.blank? + options.preview_path = "#{::Rails.root}/spec/mailers/previews" end diff --git a/lib/rspec/rails/adapters.rb b/lib/rspec/rails/adapters.rb index 63b2ed4f5c..665f2eb227 100644 --- a/lib/rspec/rails/adapters.rb +++ b/lib/rspec/rails/adapters.rb @@ -103,6 +103,7 @@ def assertion_instance assertion_modules.each do |mod| mod.public_instance_methods.each do |method| next if method == :method_missing || method == "method_missing" + define_method(method.to_sym) do |*args, &block| assertion_instance.send(method.to_sym, *args, &block) end diff --git a/lib/rspec/rails/configuration.rb b/lib/rspec/rails/configuration.rb index 6869b88437..38d67f9501 100644 --- a/lib/rspec/rails/configuration.rb +++ b/lib/rspec/rails/configuration.rb @@ -25,6 +25,7 @@ class Configuration # # @api private DIRECTORY_MAPPINGS = { + :channel => %w[spec channels], :controller => %w[spec controllers], :helper => %w[spec helpers], :job => %w[spec jobs], @@ -34,7 +35,8 @@ class Configuration :routing => %w[spec routing], :view => %w[spec views], :feature => %w[spec features], - :system => %w[spec system] + :system => %w[spec system], + :mailbox => %w[spec mailboxes] } # Sets up the different example group modules for the different spec types @@ -53,7 +55,7 @@ def self.add_test_type_configurations(config) end # @private - # rubocop:disable Style/MethodLength + # rubocop:disable Metrics/MethodLength def self.initialize_configuration(config) config.backtrace_exclusion_patterns << /vendor\// config.backtrace_exclusion_patterns << %r{lib/rspec/rails} @@ -133,15 +135,23 @@ def filter_rails_from_backtrace! end end - if defined?(ActionMailer) + if RSpec::Rails::FeatureCheck.has_action_mailer? config.include RSpec::Rails::MailerExampleGroup, :type => :mailer end - if defined?(ActiveJob) + if RSpec::Rails::FeatureCheck.has_active_job? config.include RSpec::Rails::JobExampleGroup, :type => :job end + + if RSpec::Rails::FeatureCheck.has_action_cable_testing? + config.include RSpec::Rails::ChannelExampleGroup, :type => :channel + end + + if RSpec::Rails::FeatureCheck.has_action_mailbox? + config.include RSpec::Rails::MailboxExampleGroup, :type => :mailbox + end end - # rubocop:enable Style/MethodLength + # rubocop:enable Metrics/MethodLength initialize_configuration RSpec.configuration end diff --git a/lib/rspec/rails/example.rb b/lib/rspec/rails/example.rb index 4ad8507a95..96f9594b30 100644 --- a/lib/rspec/rails/example.rb +++ b/lib/rspec/rails/example.rb @@ -9,3 +9,5 @@ require 'rspec/rails/example/job_example_group' require 'rspec/rails/example/feature_example_group' require 'rspec/rails/example/system_example_group' +require 'rspec/rails/example/channel_example_group' +require 'rspec/rails/example/mailbox_example_group' diff --git a/lib/rspec/rails/example/channel_example_group.rb b/lib/rspec/rails/example/channel_example_group.rb new file mode 100644 index 0000000000..6118c9af77 --- /dev/null +++ b/lib/rspec/rails/example/channel_example_group.rb @@ -0,0 +1,93 @@ +require "rspec/rails/matchers/action_cable/have_streams" + +module RSpec + module Rails + # @api public + # Container module for channel spec functionality. It is only available if + # ActionCable has been loaded before it. + module ChannelExampleGroup + # @private + module ClassMethods + # These blank modules are only necessary for YARD processing. It doesn't + # handle the conditional check below very well and reports undocumented objects. + end + end + end +end + +if RSpec::Rails::FeatureCheck.has_action_cable_testing? + module RSpec + module Rails + # @api public + # Container module for channel spec functionality. + module ChannelExampleGroup + extend ActiveSupport::Concern + include RSpec::Rails::RailsExampleGroup + include ActionCable::Connection::TestCase::Behavior + include ActionCable::Channel::TestCase::Behavior + + # Class-level DSL for channel specs. + module ClassMethods + # @private + def channel_class + (_channel_class || described_class).tap do |klass| + next if klass <= ::ActionCable::Channel::Base + + raise "Described class is not a channel class.\n" \ + "Specify the channel class in the `describe` statement "\ + "or set it manually using `tests MyChannelClass`" + end + end + + # @private + def connection_class + (_connection_class || described_class).tap do |klass| + next if klass <= ::ActionCable::Connection::Base + + raise "Described class is not a connection class.\n" \ + "Specify the connection class in the `describe` statement "\ + "or set it manually using `tests MyConnectionClass`" + end + end + end + + # Checks that the connection attempt has been rejected. + # + # @example + # expect { connect }.to have_rejected_connection + def have_rejected_connection + raise_error(::ActionCable::Connection::Authorization::UnauthorizedError) + end + + # Checks that the subscription is subscribed to at least one stream. + # + # @example + # expect(subscription).to have_streams + def have_streams + check_subscribed! + + RSpec::Rails::Matchers::ActionCable::HaveStream.new + end + + # Checks that the channel has been subscribed to the given stream + # + # @example + # expect(subscription).to have_stream_from("chat_1") + def have_stream_from(stream) + check_subscribed! + + RSpec::Rails::Matchers::ActionCable::HaveStream.new(stream) + end + + # Checks that the channel has been subscribed to a stream for the given model + # + # @example + # expect(subscription).to have_stream_for(user) + def have_stream_for(object) + check_subscribed! + RSpec::Rails::Matchers::ActionCable::HaveStream.new(broadcasting_for(object)) + end + end + end + end +end diff --git a/lib/rspec/rails/example/helper_example_group.rb b/lib/rspec/rails/example/helper_example_group.rb index 514e7a6c64..fc2b7ee431 100644 --- a/lib/rspec/rails/example/helper_example_group.rb +++ b/lib/rspec/rails/example/helper_example_group.rb @@ -19,6 +19,7 @@ def determine_constant_from_test_name(_ignore) else def determine_default_helper_class(_ignore) return unless Module === described_class && !(Class === described_class) + described_class end end diff --git a/lib/rspec/rails/example/mailbox_example_group.rb b/lib/rspec/rails/example/mailbox_example_group.rb new file mode 100644 index 0000000000..24cd125b60 --- /dev/null +++ b/lib/rspec/rails/example/mailbox_example_group.rb @@ -0,0 +1,80 @@ +module RSpec + module Rails + # @api public + # Container module for mailbox spec functionality. + module MailboxExampleGroup + extend ActiveSupport::Concern + + if RSpec::Rails::FeatureCheck.has_action_mailbox? + require 'action_mailbox/test_helper' + extend ::ActionMailbox::TestHelper + + # @private + def self.create_inbound_email(arg) + case arg + when Hash + create_inbound_email_from_mail(arg) + else + create_inbound_email_from_source(arg.to_s) + end + end + else + def self.create_inbound_email(_arg) + raise "Could not load ActionMailer::TestHelper" + end + end + + class_methods do + # @private + def mailbox_class + described_class + end + end + + included do + subject { described_class } + end + + # @api public + # Passes if the inbound email was delivered + # + # @example + # inbound_email = process(args) + # expect(inbound_email).to have_been_delivered + def have_been_delivered + satisfy('have been delivered', &:delivered?) + end + + # @api public + # Passes if the inbound email bounced during processing + # + # @example + # inbound_email = process(args) + # expect(inbound_email).to have_bounced + def have_bounced + satisfy('have bounced', &:bounced?) + end + + # @api public + # Passes if the inbound email failed to process + # + # @example + # inbound_email = process(args) + # expect(inbound_email).to have_failed + def have_failed + satisfy('have failed', &:failed?) + end + + # Process an inbound email message directly, bypassing routing. + # + # @param message [Hash, Mail::Message] a mail message or hash of + # attributes used to build one + # @return [ActionMaibox::InboundMessage] + def process(message) + MailboxExampleGroup.create_inbound_email(message).tap do |mail| + self.class.mailbox_class.receive(mail) + end + end + end + end +end diff --git a/lib/rspec/rails/example/mailer_example_group.rb b/lib/rspec/rails/example/mailer_example_group.rb index fe5e6dfa1d..874c773cf7 100644 --- a/lib/rspec/rails/example/mailer_example_group.rb +++ b/lib/rspec/rails/example/mailer_example_group.rb @@ -22,7 +22,7 @@ module MailerExampleGroup included do include ::Rails.application.routes.url_helpers options = ::Rails.configuration.action_mailer.default_url_options - options.each { |key, value| default_url_options[key] = value } if options + options&.each { |key, value| default_url_options[key] = value } end # Class-level DSL for mailer specs. diff --git a/lib/rspec/rails/example/system_example_group.rb b/lib/rspec/rails/example/system_example_group.rb index e63afeec8f..d4690cfb01 100644 --- a/lib/rspec/rails/example/system_example_group.rb +++ b/lib/rspec/rails/example/system_example_group.rb @@ -15,7 +15,11 @@ module SystemExampleGroup CHARS_TO_TRANSLATE = ['/', '.', ':', ',', "'", '"', " "].freeze # @private - module BlowAwayAfterTeardownHook + module BlowAwayTeardownHooks + # @private + def before_teardown + end + # @private def after_teardown end @@ -49,9 +53,7 @@ def app begin require 'capybara' require 'action_dispatch/system_test_case' - # rubocop:disable Lint/HandleExceptions rescue LoadError => e - # rubocop:enable Lint/HandleExceptions abort """ LoadError: #{e.message} System test integration requires Rails >= 5.1 and has a hard @@ -61,13 +63,18 @@ def app """.gsub(/\s+/, ' ').strip end + if ::Rails::VERSION::STRING >= '6.0' + original_before_teardown = + ::ActionDispatch::SystemTesting::TestHelpers::SetupAndTeardown.instance_method(:before_teardown) + end + original_after_teardown = ::ActionDispatch::SystemTesting::TestHelpers::SetupAndTeardown.instance_method(:after_teardown) other.include ActionDispatch::IntegrationTest::Behavior other.include ::ActionDispatch::SystemTesting::TestHelpers::SetupAndTeardown other.include ::ActionDispatch::SystemTesting::TestHelpers::ScreenshotHelper - other.include BlowAwayAfterTeardownHook + other.include BlowAwayTeardownHooks attr_reader :driver @@ -95,6 +102,9 @@ def driven_by(*args, &blk) orig_stdout = $stdout $stdout = StringIO.new begin + if ::Rails::VERSION::STRING >= '6.0' + original_before_teardown.bind(self).call + end original_after_teardown.bind(self).call ensure myio = $stdout diff --git a/lib/rspec/rails/example/view_example_group.rb b/lib/rspec/rails/example/view_example_group.rb index af9e8b64d2..1ea5a0bc15 100644 --- a/lib/rspec/rails/example/view_example_group.rb +++ b/lib/rspec/rails/example/view_example_group.rb @@ -129,14 +129,35 @@ def rendered.body def _default_render_options if ::Rails::VERSION::STRING >= '3.2' - # pluck the handler, format, and locale out of, eg, posts/index.de.html.haml - template, *components = _default_file_to_render.split('.') - handler, format, locale = *components.reverse - - render_options = { :template => template } - render_options[:handlers] = [handler] if handler - render_options[:formats] = [format.to_sym] if format - render_options[:locales] = [locale] if locale + formats = if ActionView::Template::Types.respond_to?(:symbols) + ActionView::Template::Types.symbols + else + [:html, :text, :js, :css, :xml, :json].map(&:to_s) + end.map { |x| Regexp.escape(x) }.join("|") + + handlers = ActionView::Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|") + locales = "[a-z]{2}(?:-[A-Z]{2})?" + variants = "[^.]*" + path_regex = %r{ + \A + (?