Javascript generator option with choices (#43160)

* Switch to a single controller option for choosing JavaScript approach

* Remove remnants of webpacker specific work within Rails

* No longer used

* Missing space

* Raise if unknown option is passed

* Style

* Use latest versions

* Make channels setup generic to all node setups

* Make Action Text installer work with any node package manager

* Explaining variables are not useless

* Rubocop pleasing

* Don't rely on Rails.root

Tests don't like it!

* Rubocopping

* Assume importmap

* No longer relevant

* Another cop

* Style

* Correct installation notice

* Add dependencies for action cable when adding a channel

* Fix paths to be relative to generator

* Just go straight to yarn, forget about binstub

* Fix tests

* Fixup installer, only yarn once

* Test generically with run

* Style

* Fix reference and reversibility

* Style

* Fix test

* Test pinning dependencies

* Remove extra space

* Add more tests

* Use latest dependencies

* Relegated this to controllers

* Refactor ChannelGenerator + more tests

Use a uniform level of abstraction
This commit is contained in:
David Heinemeier Hansson 2021-09-04 11:53:57 +02:00 committed by GitHub
parent 0b15b7d59c
commit 82e4432058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 250 additions and 325 deletions

@ -218,9 +218,6 @@ Lint/RedundantStringCoercion:
Lint/UriEscapeUnescape:
Enabled: true
Lint/UselessAssignment:
Enabled: true
Lint/DeprecatedClassMethods:
Enabled: true

@ -286,7 +286,7 @@ GEM
image_processing (1.12.1)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (0.3.4)
importmap-rails (0.5.0)
rails (>= 6.0.0)
jmespath (1.4.0)
json (2.5.1)
@ -467,7 +467,7 @@ GEM
sprockets (>= 3.0.0)
sqlite3 (1.4.2)
stackprof (0.2.17)
stimulus-rails (0.3.9)
stimulus-rails (0.4.0)
rails (>= 6.0.0)
sucker_punch (3.0.1)
concurrent-ruby (~> 1.0)
@ -480,7 +480,7 @@ GEM
thor (1.1.0)
tilt (2.0.10)
trailblazer-option (0.1.1)
turbo-rails (0.7.4)
turbo-rails (0.7.10)
rails (>= 6.0.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)

@ -13,51 +13,98 @@ class ChannelGenerator < NamedBase
hook_for :test_framework
def create_channel_file
template "channel.rb", File.join("app/channels", class_path, "#{file_name}_channel.rb")
def create_channel_files
create_shared_channel_files
create_channel_file
if options[:assets]
if behavior == :invoke
if defined?(Webpacker::Engine)
template "javascript/index.js", "#{Webpacker.config.source_path}/channels/index.js"
template "javascript/consumer.js", "#{Webpacker.config.source_path}/channels/consumer.js"
else
template "javascript/consumer.js", "app/javascript/channels/consumer.js"
if using_javascript?
if first_setup_required?
create_shared_channel_javascript_files
import_channels_in_javascript_entrypoint
if using_importmap?
pin_javascript_dependencies
elsif using_node?
install_javascript_dependencies
end
end
if defined?(Webpacker::Engine)
js_template "javascript/channel", File.join(Webpacker.config.source_path, "channels", class_path, "#{file_name}_channel")
else
channel_js_path = File.join("app/javascript/channels", class_path, "#{file_name}_channel")
js_template "javascript/channel", channel_js_path
gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer"
append_to_file "app/javascript/application.js", %(\nimport "channels/#{file_name}_channel"\n)
end
create_channel_javascript_file
import_channel_in_javascript_entrypoint
end
generate_application_cable_files
end
private
def create_shared_channel_files
return if behavior != :invoke
copy_file "#{__dir__}/templates/application_cable/channel.rb",
"app/channels/application_cable/channel.rb"
copy_file "#{__dir__}/templates/application_cable/connection.rb",
"app/channels/application_cable/connection.rb"
end
def create_channel_file
template "channel.rb",
File.join("app/channels", class_path, "#{file_name}_channel.rb")
end
def create_shared_channel_javascript_files
template "javascript/index.js", "app/javascript/channels/index.js"
template "javascript/consumer.js", "app/javascript/channels/consumer.js"
end
def create_channel_javascript_file
channel_js_path = File.join("app/javascript/channels", class_path, "#{file_name}_channel")
js_template "javascript/channel", channel_js_path
gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" unless using_node?
end
def import_channels_in_javascript_entrypoint
append_to_file "app/javascript/application.js",
using_node? ? %(import "./channels"\n) : %(import "channels"\n)
end
def import_channel_in_javascript_entrypoint
append_to_file "app/javascript/channels/index.js",
using_node? ? %(import "./#{file_name}_channel"\n) : %(import "channels/#{file_name}_channel"\n)
end
def install_javascript_dependencies
say "Installing JavaScript dependencies", :green
run "yarn add @rails/actioncable"
end
def pin_javascript_dependencies
append_to_file "config/importmap.rb", <<-RUBY
pin "@rails/actioncable", to: "actioncable.esm.js"
pin_all_from "app/javascript/channels", under: "channels"
RUBY
end
def file_name
@_file_name ||= super.sub(/_channel\z/i, "")
end
# FIXME: Change these files to symlinks once RubyGems 2.5.0 is required.
def generate_application_cable_files
return if behavior != :invoke
def first_setup_required?
!root.join("app/javascript/channels/index.js").exist?
end
files = [
"application_cable/channel.rb",
"application_cable/connection.rb"
]
def using_javascript?
@using_javascript ||= options[:assets] && root.join("app/javascript").exist?
end
files.each do |name|
path = File.join("app/channels/", name)
template(name, path) if !File.exist?(path)
end
def using_node?
@using_node ||= root.join("package.json").exist?
end
def using_importmap?
@using_importmap ||= root.join("config/importmap.rb").exist?
end
def root
@root ||= Pathname(destination_root)
end
end
end

@ -1,5 +1 @@
// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.
const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)
// Import all the channels to be used by Action Cable

@ -9,70 +9,42 @@ class InstallGenerator < ::Rails::Generators::Base
source_root File.expand_path("templates", __dir__)
def install_javascript_dependencies
if defined?(Webpacker::Engine)
if using_node = Pathname(destination_root).join("package.json").exist?
say "Installing JavaScript dependencies", :green
yarn_command "add #{js_dependencies.map { |name, version| "#{name}@#{version}" }.join(" ")}"
run "yarn add @rails/actiontext trix"
end
end
def append_javascript_dependencies
if defined?(Webpacker::Engine)
if (app_javascript_pack_path = Pathname.new("#{Webpacker.config.source_entry_path}/application.js")).exist?
js_dependencies.each_key do |dependency|
line = %[import "#{dependency}"]
destination = Pathname(destination_root)
unless app_javascript_pack_path.read.include? line
say "Adding #{dependency} to #{app_javascript_pack_path}", :green
append_to_file app_javascript_pack_path, "\n#{line}"
end
end
else
say <<~WARNING, :red
WARNING: Action Text can't locate your JavaScript bundle to add its package dependencies.
Add these lines to any bundles:
import "trix"
import "@rails/actiontext"
Alternatively, install and setup the webpacker gem then rerun `bin/rails action_text:install`
to have these dependencies added automatically.
WARNING
end
if (application_javascript_path = destination.join("app/javascript/application.js")).exist?
insert_into_file application_javascript_path.to_s, %(import "trix"\nimport "@rails/actiontext"\n)
else
if (application_javascript_path = Rails.root.join("app/javascript/application.js")).exist?
insert_into_file application_javascript_path.to_s, %(\nimport "trix"\nimport "@rails/actiontext")
else
say <<~INSTRUCTIONS, :green
You must import the @rails/actiontext and trix JavaScript modules in your application entrypoint.
INSTRUCTIONS
end
say <<~INSTRUCTIONS, :green
You must import the @rails/actiontext and trix JavaScript modules in your application entrypoint.
INSTRUCTIONS
end
if (importmap_path = Rails.root.join("config/importmap.rb")).exist?
insert_into_file \
importmap_path.to_s,
%( pin "trix"\n pin "@rails/actiontext", to: "actiontext.js"\n\n),
after: "Rails.application.config.importmap.draw do\n"
else
say <<~INSTRUCTIONS, :green
You must add @rails/actiontext and trix to your importmap to reference them via ESM.
INSTRUCTIONS
end
if (importmap_path = destination.join("config/importmap.rb")).exist?
append_to_file importmap_path.to_s, %(pin "trix"\npin "@rails/actiontext", to: "actiontext.js"\n)
end
end
def create_actiontext_files
template "actiontext.css", "app/assets/stylesheets/actiontext.css"
copy_file "#{GEM_ROOT}/app/views/active_storage/blobs/_blob.html.erb",
gem_root = "#{__dir__}/../../../.."
copy_file "#{gem_root}/app/views/active_storage/blobs/_blob.html.erb",
"app/views/active_storage/blobs/_blob.html.erb"
copy_file "#{GEM_ROOT}/app/views/layouts/action_text/contents/_content.html.erb",
copy_file "#{gem_root}/app/views/layouts/action_text/contents/_content.html.erb",
"app/views/layouts/action_text/contents/_content.html.erb"
end
def enable_image_processing_gem
if (gemfile_path = Rails.root.join("Gemfile")).exist?
if (gemfile_path = Pathname(destination_root).join("Gemfile")).exist?
say "Ensure image_processing gem has been enabled so image uploads will work (remember to bundle!)"
uncomment_lines gemfile_path, /gem "image_processing"/
end
@ -83,19 +55,6 @@ def create_migrations
end
hook_for :test_framework
private
GEM_ROOT = "#{__dir__}/../../../.."
def js_dependencies
js_package = JSON.load(Pathname.new("#{GEM_ROOT}/package.json"))
js_package["peerDependencies"].merge \
js_package["name"] => "^#{js_package["version"]}"
end
def yarn_command(command, config = {})
in_root { run "#{Thor::Util.ruby_command} bin/yarn #{command}", abort_on_failure: true, **config }
end
end
end
end

@ -148,13 +148,10 @@ Active Storage, with its included JavaScript library, supports uploading directl
```html
<%= javascript_include_tag "activestorage" %>
```
Requiring via importmap (as used by Stimulus) without bundling through the asset pipeline in the application html without autostart as ESM:
```js
{
"imports": {
"@rails/activestorage": "<%= asset_path "activestorage.esm" %>"
}
}
Requiring via importmap-rails without bundling through the asset pipeline in the application html without autostart as ESM:
```ruby
# config/importmap.rb
pin "@rails/activestorage", to: "activestorage.esm.js"
```
```html
<script type="module-shim">

@ -27,7 +27,6 @@ def generator_options
options[:skip_action_cable] = !defined?(ActionCable::Engine)
options[:skip_sprockets] = !defined?(Sprockets::Railtie)
options[:skip_bootsnap] = !defined?(Bootsnap)
options[:webpack] = File.exist?(Rails.root.join("config", "webpacker.yml"))
options[:updating] = true
options
end

@ -108,8 +108,8 @@ def gemfile_entries # :doc:
[rails_gemfile_entry,
database_gemfile_entry,
web_server_gemfile_entry,
webpacker_gemfile_entry,
javascript_gemfile_entry,
hotwire_gemfile_entry,
jbuilder_gemfile_entry,
psych_gemfile_entry,
cable_gemfile_entry].flatten.find_all(&@gem_filter)
@ -284,36 +284,6 @@ def rails_version_specifier(gem_version = Rails.gem_version)
end
end
# This "npm-ifies" the current version number
# With npm, versions such as "5.0.0.rc1" or "5.0.0.beta1.1" are not compliant with its
# versioning system, so they must be transformed to "5.0.0-rc1" and "5.0.0-beta1-1" respectively.
#
# "5.0.1" --> "5.0.1"
# "5.0.1.1" --> "5.0.1-1" *
# "5.0.0.rc1" --> "5.0.0-rc1"
#
# * This makes it a prerelease. That's bad, but we haven't come up with
# a better solution at the moment.
def npm_version
if options.edge? || options.main? || options.dev?
# TODO: ideally this would read from Github
# https://github.com/rails/rails/blob/main/actioncable/app/assets/javascripts/action_cable.js
# https://github.com/rails/rails/blob/main/activestorage/app/assets/javascripts/activestorage.js
# https://github.com/rails/rails/tree/main/actionview/app/assets/javascripts -> not clear where the output file is
"latest"
else
Rails.version.gsub(/\./).with_index { |s, i| i >= 2 ? "-" : s }
end
end
def webpacker_gemfile_entry
if options[:webpack]
GemfileEntry.version "webpacker", "~> 6.0.0.rc.5", "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker"
else
[]
end
end
def jbuilder_gemfile_entry
return [] if options[:skip_jbuilder]
comment = "Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder"
@ -321,22 +291,30 @@ def jbuilder_gemfile_entry
end
def javascript_gemfile_entry
importmap_rails_entry =
return [] if options[:skip_javascript]
case options[:javascript]
when "importmap"
GemfileEntry.version("importmap-rails", ">= 0.3.4", "Manage modern JavaScript using ESM without transpiling or bundling")
when "webpack"
GemfileEntry.version "webpacker", "~> 6.0.0.rc.5", "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker"
when "esbuild"
GemfileEntry.version "esbuild-rails", "~> 0.1.2", "Transpile app-like JavaScript. Read more: https://github.com/rails/esbuild-rails"
else
raise "Unknown JavaScript approach: #{options[:javascript]}"
end
end
def hotwire_gemfile_entry
return [] if options[:skip_javascript] || options[:skip_hotwire]
turbo_rails_entry =
GemfileEntry.version("turbo-rails", ">= 0.7.4", "Hotwire's SPA-like page accelerator. Read more: https://turbo.hotwired.dev")
GemfileEntry.version("turbo-rails", ">= 0.7.11", "Hotwire's SPA-like page accelerator. Read more: https://turbo.hotwired.dev")
stimulus_rails_entry =
GemfileEntry.version("stimulus-rails", ">= 0.3.9", "Hotwire's modest JavaScript framework for the HTML you already have. Read more: https://stimulus.hotwired.dev")
GemfileEntry.version("stimulus-rails", ">= 0.4.0", "Hotwire's modest JavaScript framework for the HTML you already have. Read more: https://stimulus.hotwired.dev")
if options[:skip_javascript]
[]
elsif options[:skip_hotwire]
[ importmap_rails_entry ]
else
[ importmap_rails_entry, turbo_rails_entry, stimulus_rails_entry ]
end
[ turbo_rails_entry, stimulus_rails_entry ]
end
def psych_gemfile_entry
@ -385,18 +363,6 @@ def bundle_install?
!(options[:skip_bundle] || options[:pretend])
end
def webpack_install?
options[:webpack]
end
def importmap_install?
!(options[:skip_javascript] || options[:webpack])
end
def hotwire_install?
!(options[:skip_javascript] || options[:skip_hotwire])
end
def depends_on_system_test?
!(options[:skip_system_test] || options[:skip_test] || options[:api])
end
@ -409,44 +375,18 @@ def run_bundle
bundle_command("install", "BUNDLE_IGNORE_MESSAGES" => "1") if bundle_install?
end
def run_webpack
return unless webpack_install?
def run_javascript
return if options[:skip_javascript] || !bundle_install?
unless bundle_install?
say <<~EXPLAIN
Skipping `rails webpacker:install` because `bundle install` was skipped.
To complete setup, you must run `bundle install` followed by `rails webpacker:install`.
EXPLAIN
return
case options[:javascript]
when "importmap" then rails_command "importmap:install"
when "webpack" then rails_command "webpacker:install"
when "esbuild" then rails_command "esbuild:install"
end
rails_command "webpacker:install"
end
def run_importmap
return unless importmap_install?
unless bundle_install?
say <<~EXPLAIN
Skipping `rails importmap:install` because `bundle install` was skipped.
To complete setup, you must run `bundle install` followed by `rails importmap:install`.
EXPLAIN
return
end
rails_command "importmap:install"
end
def run_hotwire
return unless hotwire_install?
unless bundle_install?
say <<~EXPLAIN
Skipping `rails turbo:install stimulus:install` because `bundle install` was skipped.
To complete setup, you must run `bundle install` followed by `rails turbo:install stimulus:install`.
EXPLAIN
return
end
return if options[:skip_javascript] || options[:skip_hotwire] || !bundle_install?
rails_command "turbo:install stimulus:install"
end

@ -267,20 +267,11 @@ class AppGenerator < AppBase
add_shared_options_for "application"
# Add rails command options
class_option :version, type: :boolean, aliases: "-v", group: :rails,
desc: "Show Rails version number and quit"
class_option :api, type: :boolean,
desc: "Preconfigure smaller stack for API only apps"
class_option :minimal, type: :boolean,
desc: "Preconfigure a minimal rails app"
class_option :skip_bundle, type: :boolean, aliases: "-B", default: false,
desc: "Don't run bundle install"
class_option :webpack, type: :boolean, aliases: "--webpacker", default: false,
desc: "Preconfigure Webpack"
class_option :version, type: :boolean, aliases: "-v", group: :rails, desc: "Show Rails version number and quit"
class_option :api, type: :boolean, desc: "Preconfigure smaller stack for API only apps"
class_option :minimal, type: :boolean, desc: "Preconfigure a minimal rails app"
class_option :javascript, type: :string, aliases: "-j", default: "importmap", desc: "Choose JavaScript approach"
class_option :skip_bundle, type: :boolean, aliases: "-B", default: false, desc: "Don't run bundle install"
def initialize(*args)
super
@ -308,11 +299,7 @@ def initialize(*args)
skip_javascript: true,
skip_jbuilder: true,
skip_system_test: true,
skip_hotwire: true).tap do |option|
if option[:webpack]
option[:skip_javascript] = false
end
end.freeze
skip_hotwire: true).freeze
end
@after_bundle_callbacks = []
@ -523,8 +510,7 @@ def finish_template
public_task :apply_rails_template, :run_bundle
public_task :generate_bundler_binstub
public_task :run_webpack
public_task :run_importmap
public_task :run_javascript
public_task :run_hotwire
def run_after_bundle_callbacks

@ -11,11 +11,6 @@
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
<%- if options[:webpack] -%>
# # If you are using webpack-dev-server then specify webpack-dev-server host
# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
<%- end -%>
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end

@ -5,10 +5,5 @@
db/schema.rb linguist-generated
<% end -%>
<% if options[:webpack] -%>
# Mark the yarn lockfile as having been generated.
yarn.lock linguist-generated
<% end -%>
# Mark any vendored files as having been vendored.
vendor/* linguist-vendored

@ -9,10 +9,6 @@
/<%= dummy_path %>/db/*.sqlite3-*
<% end -%>
/<%= dummy_path %>/log/*.log
<% if options[:webpack] -%>
/<%= dummy_path %>/node_modules/
/<%= dummy_path %>/yarn-error.log
<% end -%>
<% unless skip_active_storage? -%>
/<%= dummy_path %>/storage/
<% end -%>

@ -3,86 +3,71 @@
require "generators/generators_test_helper"
require "generators/action_text/install/install_generator"
module Webpacker
extend self
def config
Class.new do
def source_path
"app/packs"
end
def source_entry_path
"app/packs/entrypoints"
end
end.new
end
end
class ActionText::Generators::InstallGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
setup do
Rails.application = Rails.application.class
Rails.application.config.root = Pathname(destination_root)
run_under_webpacker
FileUtils.mkdir_p("#{destination_root}/app/javascript")
FileUtils.touch("#{destination_root}/app/javascript/application.js")
FileUtils.mkdir_p("#{destination_root}/config")
FileUtils.touch("#{destination_root}/config/importmap.rb")
end
teardown do
Rails.application = Rails.application.instance
run_under_asset_pipeline
end
Rails.application = Rails.application.instance
end
test "installs JavaScript dependencies" do
run_generator_instance
yarn_commands = @yarn_commands.join("\n")
FileUtils.touch("#{destination_root}/package.json")
assert_match %r"^add .*@rails/actiontext@", yarn_commands
assert_match %r"^add .*trix@", yarn_commands
run_generator_instance
assert_match %r"yarn add @rails/actiontext trix", @run_commands.join("\n")
end
test "throws warning for incomplete webpacker configuration" do
output = run_generator_instance
expected = "WARNING: Action Text can't locate your JavaScript bundle to add its package dependencies."
assert_match expected, output
test "throws warning for missing entry point" do
FileUtils.rm("#{destination_root}/app/javascript/application.js")
assert_match "You must import the @rails/actiontext and trix JavaScript modules", run_generator_instance
end
test "loads JavaScript dependencies in application.js" do
application_js = Pathname("app/javascript/application.js").expand_path(destination_root)
application_js.dirname.mkpath
application_js.write("\n")
run_under_asset_pipeline
test "imports JavaScript dependencies in application.js" do
run_generator_instance
assert_file application_js do |content|
assert_file "app/javascript/application.js" do |content|
assert_match %r"^#{Regexp.escape 'import "@rails/actiontext"'}", content
assert_match %r"^#{Regexp.escape 'import "trix"'}", content
end
end
test "creates Action Text stylesheet" do
test "pins JavaScript dependencies in importmap.rb" do
run_generator_instance
assert_file "config/importmap.rb" do |content|
assert_match %r|pin "@rails/actiontext"|, content
assert_match %r|pin "trix"|, content
end
end
test "creates Action Text stylesheet" do
run_generator_instance
assert_file "app/assets/stylesheets/actiontext.css"
end
test "creates Active Storage view partial" do
run_generator_instance
assert_file "app/views/active_storage/blobs/_blob.html.erb"
end
test "creates Action Text content view layout" do
run_generator_instance
assert_file "app/views/layouts/action_text/contents/_content.html.erb"
end
test "creates migrations" do
run_generator_instance
assert_migration "db/migrate/create_active_storage_tables.active_storage.rb"
assert_migration "db/migrate/create_action_text_tables.action_text.rb"
end
@ -99,36 +84,13 @@ class ActionText::Generators::InstallGeneratorTest < Rails::Generators::TestCase
end
end
test "run just for asset pipeline" do
run_under_asset_pipeline
application_js = Pathname("app/javascript/application.js").expand_path(destination_root)
application_js.dirname.mkpath
application_js.write ""
run_generator_instance
assert_file application_js do |content|
assert_match %r"trix", content
end
end
private
def run_generator_instance
@yarn_commands = []
yarn_command_stub = -> (command, *) { @yarn_commands << command }
@run_commands = []
run_command_stub = -> (command, *) { @run_commands << command }
generator.stub :yarn_command, yarn_command_stub do
generator.stub :run, run_command_stub do
with_database_configuration { super }
end
end
def run_under_webpacker
# Stub Webpacker engine presence to exercise path
Kernel.silence_warnings { Webpacker.const_set(:Engine, true) } rescue nil
end
def run_under_asset_pipeline
Kernel.silence_warnings { Webpacker.send(:remove_const, :Engine) } rescue nil
end
end

@ -208,15 +208,6 @@ def test_new_application_load_defaults
assert_file "#{app_root}/config/application.rb", /\s+config\.load_defaults #{Rails::VERSION::STRING.to_f}/
end
def test_csp_initializer_include_connect_src_example
app_root = File.join(destination_root, "myapp")
run_generator [app_root, "--webpack"]
assert_file "#{app_root}/config/initializers/content_security_policy.rb" do |content|
assert_match(/# policy\.connect_src/, content)
end
end
def test_app_update_keep_the_cookie_serializer_if_it_is_already_configured
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@ -774,8 +765,8 @@ def test_skip_javascript_option
generator([destination_root], skip_javascript: true)
command_check = -> command, *_ do
if command == "webpacker:install"
flunk "`webpacker:install` expected to not be called."
if command == "importmap:install"
flunk "`importmap:install` expected to not be called."
end
end
@ -783,7 +774,7 @@ def test_skip_javascript_option
run_generator_instance
end
assert_no_gem "webpacker"
assert_no_gem "importmap-rails"
assert_file "config/initializers/content_security_policy.rb" do |content|
assert_no_match(/policy\.connect_src/, content)
@ -795,7 +786,7 @@ def test_skip_javascript_option
end
def test_webpack_option
generator([destination_root], webpack: true)
generator([destination_root], javascript: "webpack")
webpacker_called = 0
command_check = -> command, *_ do
@ -822,7 +813,7 @@ def test_hotwire
end
assert_file "app/javascript/application.js" do |content|
assert_match(/turbo/, content)
assert_match(/stimulus/, content)
assert_match(/controllers/, content)
end
end

@ -2,17 +2,18 @@
require "generators/generators_test_helper"
require "rails/generators/channel/channel_generator"
require "byebug"
class ChannelGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
tests Rails::Generators::ChannelGenerator
setup do
FileUtils.mkdir_p("#{destination_root}/app/javascript")
FileUtils.touch("#{destination_root}/app/javascript/application.js")
use_with_javascript
use_under_importmap
end
def test_application_cable_skeleton_is_created
test "shared channel files are created" do
run_generator ["books"]
assert_file "app/channels/application_cable/channel.rb" do |cable|
@ -24,7 +25,7 @@ def test_application_cable_skeleton_is_created
end
end
def test_channel_is_created
test "specific channel files are created under importmap" do
run_generator ["chat"]
assert_file "app/channels/chat_channel.rb" do |channel|
@ -36,7 +37,18 @@ def test_channel_is_created
end
end
def test_channel_with_multiple_actions_is_created
test "specific channel files are created under node" do
use_under_node
generator(["chat"]).stub(:install_javascript_dependencies, true) do
run_generator_instance
assert_file "app/javascript/channels/chat_channel.js" do |channel|
assert_match(/import consumer from ".\/consumer"\s+consumer\.subscriptions\.create\("ChatChannel/, channel)
end
end
end
test "channel with multiple actions is created" do
run_generator ["chat", "speak", "mute"]
assert_file "app/channels/chat_channel.rb" do |channel|
@ -52,7 +64,51 @@ def test_channel_with_multiple_actions_is_created
end
end
def test_channel_asset_is_not_created_when_skip_assets_is_passed
test "shared channel javascript files are created" do
run_generator ["books"]
assert_file "app/javascript/channels/index.js"
assert_file "app/javascript/channels/consumer.js"
end
test "import channels in javascript entrypoint" do
run_generator ["books"]
assert_file "app/javascript/application.js" do |entrypoint|
assert_match %r|import "channels"|, entrypoint
end
end
test "import channels in javascript entrypoint under node" do
use_under_node
generator(["chat"]).stub(:install_javascript_dependencies, true) do
run_generator_instance
assert_file "app/javascript/application.js" do |entrypoint|
assert_match %r|import "./channels"|, entrypoint
end
end
end
test "pin javascript dependencies" do
run_generator ["chat"]
assert_file "config/importmap.rb" do |content|
assert_match %r|pin "@rails/actioncable"|, content
assert_match %r|pin_all_from "app/javascript/channels"|, content
end
end
test "first setup only happens once" do
run_generator ["chat"]
assert_file "app/javascript/channels/consumer.js"
FileUtils.rm("#{destination_root}/app/javascript/channels/consumer.js")
run_generator ["another"]
assert_no_file "app/javascript/channels/consumer.js"
end
test "javascripts not generated when assets are skipped" do
run_generator ["chat", "--skip-assets"]
assert_file "app/channels/chat_channel.rb" do |channel|
@ -62,15 +118,7 @@ def test_channel_asset_is_not_created_when_skip_assets_is_passed
assert_no_file "app/javascript/channels/chat_channel.js"
end
def test_consumer_js_is_created_if_not_present_already
run_generator ["chat"]
FileUtils.rm("#{destination_root}/app/javascript/channels/consumer.js")
run_generator ["camp"]
assert_file "app/javascript/channels/consumer.js"
end
def test_invokes_default_test_framework
test "invokes default test framework" do
run_generator %w(chat -t=test_unit)
assert_file "test/channels/chat_channel_test.rb" do |test|
@ -80,7 +128,7 @@ def test_invokes_default_test_framework
end
end
def test_channel_on_revoke
test "revoking" do
run_generator ["chat"]
run_generator ["chat"], behavior: :revoke
@ -93,7 +141,7 @@ def test_channel_on_revoke
assert_file "app/javascript/channels/consumer.js"
end
def test_channel_suffix_is_not_duplicated
test "channel suffix is not duplicated" do
run_generator ["chat_channel"]
assert_no_file "app/channels/chat_channel_channel.rb"
@ -105,4 +153,21 @@ def test_channel_suffix_is_not_duplicated
assert_no_file "test/channels/chat_channel_channel_test.rb"
assert_file "test/channels/chat_channel_test.rb"
end
private
def use_with_javascript
FileUtils.mkdir_p("#{destination_root}/app/javascript")
FileUtils.touch("#{destination_root}/app/javascript/application.js")
end
def use_under_importmap
FileUtils.mkdir_p("#{destination_root}/config")
FileUtils.touch("#{destination_root}/config/importmap.rb")
FileUtils.rm_rf("#{destination_root}/package.json")
end
def use_under_node
FileUtils.touch("#{destination_root}/package.json")
FileUtils.rm_rf("#{destination_root}/config/importmap.rb")
end
end