[904] Enable testing component JS interactions using new with_rendered_component_path method (#1061)

* Add render_in_browser test helper method to test JS interactions

* Update test to test simple JS interactions

* Fix rubocop violations

* Add test coverage for with or without layout

* Allow tmp file to be included to handle temp file generations

* Fix more rubocop complaints

* Exclude JS interaction test with layout specified

* Change process_timeout on cuprite to address brittleness

* Add new BrowserTestCase specified for in-browser tests of componnets

* Attempt to utilize system test approach

* Add selenium to appease failing specs (not sure why tbh!)

* Bump to redo CI

* Enable changing layout with a parameter in visit_rendered_in_browser

* Update docs/CHANGELOG.md

* Update docs/CHANGELOG.md

* Update docs/CHANGELOG.md

* Fix broken specs

* Remove unneccesary .gitkeep on tmp

* Create tmp folder if it doesn't exist

* Fix linting error

* Increase wait time on capybara & remove unneccesary comments

* Add test coverage for when you use or not use a layout in system tests

* Add guide on how to test interactions

* Fix lint errors

* Secure the system test endpoint and use specific directory for tmp components

* Address rubocop violations

* Fix problem with the mkdir

* Update condition to check for being in production

* Strictly only set routes used for system_tests under RAILS_ENV=test

* Place the gems used for system_test to only be included in test environment

* Update test to use new block syntax

* Address linting issues reported by standardrb

* Update Gemfile

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

* Update Gemfile

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

* Update app/controllers/view_components_controller.rb

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

* Revert removal of tmp from .gitignore

* Update Gemfile.lock

* Revert gitignore change that is not needed

* Update testing documentation

* Apply renaming suggestions for entry point

* Remove extra comment that is not needed

* Further expand max_wait_time for capybara test to avoid test failures

* Remove need to create dedicated folder in tmp for system test

* Remove extra  option for system test route drawing

* Remove definition of template in with_rendered_component_in_browser

* Update Gemfile.lock

* Test

* Add back in tmp directory to address test failures

* Update Gemfile

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

* Update test Gems to have coarse dependency pointers

* Remove redundant status: 200 on system_test_entrypoint

* Change test helper method to be clear why are not providing the browser, only the path!

* Move the system_test_entrypoint to the PreviewsAction module

* Conditionally use selenium 3 when using ruby 2.5 or below

* Add option to incrase process_timeout on cuprite

* Fix formatting issue on CHANGELOG.md

* Update timeouts on cuprite

* Update docs/CHANGELOG.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

Update lib/view_component/engine.rb

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

* Undo a change that was not intended

* Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

* Update docs/guide/testing.md

Co-authored-by: Joel Hawksley <joelhawksley@github.com>

* Decouple capybara from ViewComponents

* Apply suggestions from code review

* Add basic test for passing in parameters into component

* Update API to use fragements to support slots

* Update the method to be a bit more clear

* Update for linting issues

* Fix the method name

* Fix linting issue

* Place the html_safe in the class

* Update API to match aligned naming

* Update documentation for testing using new approach

* Update yardoc

* Remove empty line

* Apply suggestions from code review

* re-add errant removal

* remove errant whitespace

* render fragment directly instead of via preview template

* reuse component instead of duplicating html

* remove change to preview template

* simplify example

* simplify test helper

* simplify test helper

* prefix internal route with underscore

* increase process_timeout

Co-authored-by: Joel Hawksley <joel@hawksley.org>
Co-authored-by: Joel Hawksley <joelhawksley@github.com>
This commit is contained in:
Edwin Mak 2022-12-12 12:50:53 -05:00 коммит произвёл GitHub
Родитель 8026219f3b
Коммит 93a67c39b9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 221 добавлений и 9 удалений

12
Gemfile
Просмотреть файл

@ -10,6 +10,18 @@ gem "rails", (rails_version == "main") ? {git: "https://github.com/rails/rails",
gem "rspec-rails", "~> 5"
group :test do
gem "cuprite", "~> 0.8"
gem "puma", "~> 5"
if RUBY_VERSION >= "2.6"
gem "selenium-webdriver", "~> 4"
else
# Selenium 4 requires Ruby 2.6+
gem "selenium-webdriver", "~> 3"
end
end
if RUBY_VERSION >= "3.1"
gem "net-imap", require: false
gem "net-pop", require: false

Просмотреть файл

@ -92,7 +92,7 @@ GEM
parser (>= 2.4)
smart_properties
builder (3.2.4)
capybara (3.37.1)
capybara (3.38.0)
addressable
matrix
mini_mime (>= 0.1.3)
@ -101,9 +101,13 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
childprocess (4.1.0)
coderay (1.1.3)
concurrent-ruby (1.1.10)
crass (1.0.6)
cuprite (0.14.3)
capybara (~> 3.0)
ferrum (~> 0.13.0)
diff-lcs (1.5.0)
docile (1.4.0)
erb_lint (0.0.37)
@ -115,6 +119,11 @@ GEM
rubocop
smart_properties
erubi (1.11.0)
ferrum (0.13)
addressable (~> 2.5)
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (>= 0.6, < 0.8)
globalid (1.0.0)
activesupport (>= 5.0)
haml (5.2.2)
@ -130,7 +139,7 @@ GEM
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
m (1.6.0)
m (1.6.1)
method_source (>= 0.6.7)
rake (>= 0.9.2.2)
mail (2.7.1)
@ -160,6 +169,8 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.0)
puma (5.6.5)
nio4r (~> 2.0)
racc (1.6.0)
rack (2.2.4)
rack-test (2.0.2)
@ -192,7 +203,7 @@ GEM
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.6.0)
regexp_parser (2.6.1)
rexml (3.2.5)
rspec-core (3.12.0)
rspec-support (~> 3.12.0)
@ -211,7 +222,7 @@ GEM
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.12.0)
rubocop (1.38.0)
rubocop (1.39.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
@ -223,10 +234,16 @@ GEM
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.23.0)
parser (>= 3.1.1.0)
rubocop-performance (1.15.0)
rubocop-performance (1.15.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
ruby-progressbar (1.11.0)
rubyzip (2.3.2)
selenium-webdriver (4.6.1)
childprocess (>= 0.5, < 5.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
simplecov (0.18.5)
docile (~> 1.1)
simplecov-html (~> 0.11)
@ -246,9 +263,9 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
standard (1.17.0)
rubocop (= 1.38.0)
rubocop-performance (= 1.15.0)
standard (1.18.1)
rubocop (= 1.39.0)
rubocop-performance (= 1.15.1)
temple (0.8.2)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
@ -259,6 +276,7 @@ GEM
concurrent-ruby (~> 1.0)
unicode-display_width (2.3.0)
webrick (1.7.0)
websocket (1.2.9)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@ -268,7 +286,7 @@ GEM
webrick (~> 1.7.0)
yard-activesupport-concern (0.0.1)
yard (>= 0.8)
zeitwerk (2.6.4)
zeitwerk (2.6.6)
PLATFORMS
ruby
@ -279,15 +297,18 @@ DEPENDENCIES
better_html (~> 1)
bundler (~> 2)
capybara (~> 3)
cuprite (~> 0.8)
erb_lint (~> 0.0.37)
haml (~> 5)
jbuilder (~> 2)
m (~> 1)
minitest (= 5.6.0)
pry (~> 0.13)
puma (~> 5)
rails (~> 7.0.0)
rake (~> 13.0)
rspec-rails (~> 5)
selenium-webdriver (~> 4)
simplecov (~> 0.18.0)
simplecov-console (~> 0.7.2)
slim (~> 4.0)

Просмотреть файл

@ -39,6 +39,12 @@ module ViewComponent
end
end
if Rails.env.test?
def system_test_entrypoint
render file: "./tmp/view_components/#{params.permit(:file)[:file]}"
end
end
private
# :doc:

Просмотреть файл

@ -10,6 +10,10 @@ nav_order: 5
## main
* Add `with_rendered_component_path` helper for writing component system tests.
*Edwin Mak*
* Include gem name and deprecation horizon in every deprecation message.
*Jan Klimo*

Просмотреть файл

@ -233,3 +233,33 @@ To use component previews:
# config/application.rb
config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
```
## Component system tests
Use `with_rendered_component_path` with `render_inline` to system test components:
```rb
class ViewComponentSystemTest < ViewComponent::SystemTestCase
def test_simple_js_interaction_in_browser_without_layout
with_rendered_component_path(render_inline(SimpleJavascriptInteractionWithJsIncludedComponent.new)) do |path|
visit(path)
assert(find("[data-hidden-field]", visible: false))
find("[data-button]", text: "Click Me To Reveal Something Cool").click
assert(find("[data-hidden-field]", visible: true))
end
end
end
```
For components that depend on a layout, provide the `layout` argument:
```rb
class ViewComponentSystemTest < ViewComponent::SystemTestCase
def test_simple_js_interaction_in_browser_with_layout
with_rendered_component_path(render_inline(SimpleJavascriptInteractionWithoutJsIncludedComponent.new), layout: 'application') do |path|
# ...
end
end
end
```

Просмотреть файл

@ -16,7 +16,9 @@ module ViewComponent
autoload :Preview
autoload :PreviewTemplateError
autoload :TestHelpers
autoload :SystemTestHelpers
autoload :TestCase
autoload :SystemTestCase
autoload :TemplateError
autoload :Translatable
end

Просмотреть файл

@ -121,6 +121,10 @@ module ViewComponent
internal: true
)
if Rails.env.test?
get("_system_test_entrypoint", to: "#{preview_controller}#system_test_entrypoint")
end
get(
"#{options.preview_route}/*path",
to: "#{preview_controller}#previews",

Просмотреть файл

@ -0,0 +1,13 @@
# frozen_string_literal: true
require "active_support/test_case"
module ViewComponent
class SystemTestCase < ActionDispatch::SystemTestCase
include ViewComponent::SystemTestHelpers
def page
Capybara.current_session
end
end
end

Просмотреть файл

@ -0,0 +1,27 @@
# frozen_string_literal: true
module ViewComponent
module SystemTestHelpers
include TestHelpers
#
# Returns a block that can be used to visit the path of the inline rendered component.
# @param fragment [Nokogiri::Fragment] The fragment returned from `render_inline`.
# @param layout [String] The (optional) layout to use.
# @return [Proc] A block that can be used to visit the path of the inline rendered component.
def with_rendered_component_path(fragment, layout: false, &block)
# Add './tmp/view_components/' directory if it doesn't exist to store the rendered component HTML
FileUtils.mkdir_p("./tmp/view_components/") unless Dir.exist?("./tmp/view_components/")
file = Tempfile.new(["rendered_#{fragment.class.name}", ".html"], "tmp/view_components/")
begin
file.write(controller.render_to_string(html: fragment.to_html.html_safe, layout: layout))
file.rewind
block.call("/_system_test_entrypoint?file=#{file.path.split("/").last}")
ensure
file.unlink
end
end
end
end

Просмотреть файл

@ -0,0 +1,11 @@
<script>
document.addEventListener('DOMContentLoaded', function(event) {
let button = document.querySelector('[data-button]')
let hiddenField = document.querySelector('[data-hidden-field]')
button.addEventListener('click', function() {
hiddenField.style.display = "block"
})
})
</script>
<%= render(SimpleJavascriptInteractionWithoutJsIncludedComponent.new) %>

Просмотреть файл

@ -0,0 +1,4 @@
# frozen_string_literal: true
class SimpleJavascriptInteractionWithJsIncludedComponent < ViewComponent::Base
end

Просмотреть файл

@ -0,0 +1,4 @@
<button id='button' data-button>Click Me To Reveal Something Cool</button>
<div data-hidden-field style="display: none">
Now you see me!
</div>

Просмотреть файл

@ -0,0 +1,4 @@
# frozen_string_literal: true
class SimpleJavascriptInteractionWithoutJsIncludedComponent < ViewComponent::Base
end

Просмотреть файл

@ -8,3 +8,13 @@
<%= yield %>
</body>
</html>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
let button = document.querySelector('[data-button]')
let hiddenField = document.querySelector('[data-hidden-field]')
button.addEventListener('click', function() {
hiddenField.style.display = "block"
})
})
</script>

Просмотреть файл

@ -0,0 +1,47 @@
# frozen_string_literal: true
require "test_helper"
class ViewComponentSystemTest < ViewComponent::SystemTestCase
driven_by :cuprite
def test_simple_js_interaction_in_browser_without_layout
with_rendered_component_path(render_inline(SimpleJavascriptInteractionWithJsIncludedComponent.new)) do |path|
visit path
assert find("[data-hidden-field]", visible: false)
find("[data-button]", text: "Click Me To Reveal Something Cool").click
assert find("[data-hidden-field]", visible: true)
end
end
def test_simple_js_interaction_in_browser_with_layout
with_rendered_component_path(render_inline(SimpleJavascriptInteractionWithoutJsIncludedComponent.new), layout: "application") do |path|
visit path
assert find("[data-hidden-field]", visible: false)
find("[data-button]", text: "Click Me To Reveal Something Cool").click
assert find("[data-hidden-field]", visible: true)
end
end
def test_component_with_params
with_rendered_component_path(render_inline(TitleWrapperComponent.new(title: "awesome-title"))) do |path|
visit path
assert find("div", text: "awesome-title")
end
end
def test_components_with_slots
with_rendered_component_path(render_inline(SlotsV2Component.new) do |component|
component.title do
"This is my title!"
end
end) do |path|
visit path
find(".title", text: "This is my title!")
end
end
end

Просмотреть файл

@ -37,6 +37,19 @@ ViewComponent::Deprecation.behavior = :silence
require File.expand_path("sandbox/config/environment.rb", __dir__)
require "rails/test_help"
require "capybara/cuprite"
Capybara.register_driver(:cuprite) do |app|
# Add the process_timeout option to prevent failures due to the browser
# taking too long to start up.
Capybara::Cuprite::Driver.new(app, {process_timeout: 60, timeout: 30})
end
# Reduce extra logs produced by puma booting up
Capybara.server = :puma, {Silent: true}
# Increase the max wait time to appease test failures due to timeouts.
Capybara.default_max_wait_time = 10
def with_config_option(option_name, new_value, config_entrypoint: Rails.application.config.view_component)
old_value = config_entrypoint.public_send(option_name)
config_entrypoint.public_send("#{option_name}=", new_value)