From 83c42e8deb61aec916ddeb75e00bf0d8546c64e3 Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Sat, 10 Jun 2023 20:19:15 +0330 Subject: [PATCH] Add generator test (#300) * Add base rails dummy app for test * Add test for bin/setup and peer dependencies * Add e2e test after installation --------- Co-authored-by: Judah Meek --- .github/workflows/generator.yml | 37 ++++ CONTRIBUTING.md | 14 +- Rakefile | 13 +- .../files/app/controllers/home_controller.rb | 4 + .../files/app/javascript/components/App.js | 12 ++ .../files/app/javascript/packs/application.js | 10 ++ .../files/app/views/home/index.html.erb | 2 + .../app/views/layouts/application.html.erb | 17 ++ .../e2e_template/files/config/routes.rb | 3 + .../test_react_component_renders_spec.rb | 13 ++ spec/generator_specs/e2e_template/template.rb | 21 +++ spec/generator_specs/generator_spec.rb | 169 ++++++++++++++++++ 12 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/generator.yml create mode 100644 spec/generator_specs/e2e_template/files/app/controllers/home_controller.rb create mode 100644 spec/generator_specs/e2e_template/files/app/javascript/components/App.js create mode 100644 spec/generator_specs/e2e_template/files/app/javascript/packs/application.js create mode 100644 spec/generator_specs/e2e_template/files/app/views/home/index.html.erb create mode 100644 spec/generator_specs/e2e_template/files/app/views/layouts/application.html.erb create mode 100644 spec/generator_specs/e2e_template/files/config/routes.rb create mode 100644 spec/generator_specs/e2e_template/files/spec/system/test_react_component_renders_spec.rb create mode 100644 spec/generator_specs/e2e_template/template.rb create mode 100644 spec/generator_specs/generator_spec.rb diff --git a/.github/workflows/generator.yml b/.github/workflows/generator.yml new file mode 100644 index 000000000..53c9f61a3 --- /dev/null +++ b/.github/workflows/generator.yml @@ -0,0 +1,37 @@ +name: Generator specs + +on: [push] + +jobs: + test: + name: Generator specs + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + ruby: ['2.6', '2.7', '3.0'] + gemfile: + - gemfiles/Gemfile-rails.6.0.x + - gemfiles/Gemfile-rails.6.1.x + - gemfiles/Gemfile-rails.7.0.x + # Uncomment the following line only to ensure compatibility with the + # upcomming Rails versions, maybe before a release. + #- gemfiles/Gemfile-rails-edge + exclude: + - ruby: 2.6 + os: ubuntu-latest + gemfile: gemfiles/Gemfile-rails.7.0.x + - ruby: 2.6 + os: ubuntu-latest + gemfile: gemfiles/Gemfile-rails-edge + env: + BUNDLE_GEMFILE: ${{ matrix.gemfile }} + + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Install dependencies + run: bundle install + - run: bundle exec rake run_spec:generator diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 99883432c..200cb16ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,15 +78,17 @@ For this, you need `yalc` to be installed on your local machine bundle exec rake run_spec:dummy ``` -## Testing the generator -If you change the generator, check that install instructions work. +#### 4.6 Testing the installer +To ensure that your installer works as expected, either you can run `bundle exec rake run_spec:install`, or take the following manual testing steps: -1. Update the Gemfile so that gem "shakapacker" has a line like this, pointing to your install of Shakapacker +1. Update the `Gemfile` so that gem `shakapacker` has a line like this, pointing to your developing Shakapacker: ```ruby - gem 'shakapacker', path: "~/shakacode/forks/shakapacker" + gem 'shakapacker', path: "relative_or_absolute_path_to_the_gem" ``` -2. `bundle` -3. Run the generator to confirm that you got the right changes. +2. Run `bundle install` to install the updated gem. +3. Run `bundle exec rails shakapacker:install` to confirm that you got the right changes. + + **Note:** Ensure that you use bundle exec otherwise the installed shakapacker gem will run and not the one you are working on. ## Find existing issues You may look at the issues list to find existing known issues to be addressed. In this, we recommend looking at closed issues, particularly with the "[help wanted](https://github.com/shakacode/shakapacker/issues?q=is%3Aissue+label%3A%22help+wanted%22+is%3Aclosed+)" label. \ No newline at end of file diff --git a/Rakefile b/Rakefile index 4c17fc344..0ce552f55 100644 --- a/Rakefile +++ b/Rakefile @@ -11,13 +11,13 @@ namespace :run_spec do desc "Run shakapacker specs" task :gem do puts "Running Shakapacker gem specs" - system("bundle exec rspec spec/shakapacker/*_spec.rb") + sh("bundle exec rspec spec/shakapacker/*_spec.rb") end desc "Run backward compatibility specs" task :gem_bc do puts "Running Shakapacker gem specs for backward compatibility" - system("bundle exec rspec spec/backward_compatibility_specs/*_spec_bc.rb") + sh("bundle exec rspec spec/backward_compatibility_specs/*_spec_bc.rb") end desc "Run specs in the dummy app" @@ -35,12 +35,17 @@ namespace :run_spec do end end + desc "Run generator specs" + task :generator do + sh("bundle exec rspec spec/generator_specs/*_spec.rb") + end + desc "Run all specs" - task all_specs: %i[gem gem_bc dummy] do + task all_specs: %i[gem gem_bc dummy generator] do puts "Completed all RSpec tests" end end def sh_in_dir(dir, *shell_commands) - shell_commands.flatten.each { |shell_command| sh %(cd #{dir} && #{shell_command.strip}) } + Shakapacker::Utils::Misc.sh_in_dir(dir, *shell_commands) end diff --git a/spec/generator_specs/e2e_template/files/app/controllers/home_controller.rb b/spec/generator_specs/e2e_template/files/app/controllers/home_controller.rb new file mode 100644 index 000000000..95f29929c --- /dev/null +++ b/spec/generator_specs/e2e_template/files/app/controllers/home_controller.rb @@ -0,0 +1,4 @@ +class HomeController < ApplicationController + def index + end +end diff --git a/spec/generator_specs/e2e_template/files/app/javascript/components/App.js b/spec/generator_specs/e2e_template/files/app/javascript/components/App.js new file mode 100644 index 000000000..f9a3a57b9 --- /dev/null +++ b/spec/generator_specs/e2e_template/files/app/javascript/components/App.js @@ -0,0 +1,12 @@ +import React from 'react' + +function App() { + return ( +
+

App component

+

Text from react component!

+
+ ) +} + +export default App diff --git a/spec/generator_specs/e2e_template/files/app/javascript/packs/application.js b/spec/generator_specs/e2e_template/files/app/javascript/packs/application.js new file mode 100644 index 000000000..8afa4599d --- /dev/null +++ b/spec/generator_specs/e2e_template/files/app/javascript/packs/application.js @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from '../components/App' + +const root = ReactDOM.createRoot(document.getElementById('root')) +root.render( + + + +) diff --git a/spec/generator_specs/e2e_template/files/app/views/home/index.html.erb b/spec/generator_specs/e2e_template/files/app/views/home/index.html.erb new file mode 100644 index 000000000..2085730c7 --- /dev/null +++ b/spec/generator_specs/e2e_template/files/app/views/home/index.html.erb @@ -0,0 +1,2 @@ +

Home#index

+

Find me in app/views/home/index.html.erb

diff --git a/spec/generator_specs/e2e_template/files/app/views/layouts/application.html.erb b/spec/generator_specs/e2e_template/files/app/views/layouts/application.html.erb new file mode 100644 index 000000000..0b4199598 --- /dev/null +++ b/spec/generator_specs/e2e_template/files/app/views/layouts/application.html.erb @@ -0,0 +1,17 @@ + + + + Sh7InstallTest + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application" %> + <%= javascript_pack_tag "application" %> + + + + <%= yield %> +
+ + diff --git a/spec/generator_specs/e2e_template/files/config/routes.rb b/spec/generator_specs/e2e_template/files/config/routes.rb new file mode 100644 index 000000000..184b15f7e --- /dev/null +++ b/spec/generator_specs/e2e_template/files/config/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + get "home/index" +end diff --git a/spec/generator_specs/e2e_template/files/spec/system/test_react_component_renders_spec.rb b/spec/generator_specs/e2e_template/files/spec/system/test_react_component_renders_spec.rb new file mode 100644 index 000000000..658020fc1 --- /dev/null +++ b/spec/generator_specs/e2e_template/files/spec/system/test_react_component_renders_spec.rb @@ -0,0 +1,13 @@ +require "rails_helper" + +RSpec.describe "home/index", type: :system, js: true do + before do + driven_by(:selenium_headless) + end + + it "renders the App component" do + visit "home/index" + + expect(page.body).to match "App component" + end +end diff --git a/spec/generator_specs/e2e_template/template.rb b/spec/generator_specs/e2e_template/template.rb new file mode 100644 index 000000000..9e135e2d6 --- /dev/null +++ b/spec/generator_specs/e2e_template/template.rb @@ -0,0 +1,21 @@ +# install react +system("yarn add react react-dom @babel/preset-react") + +# update webpack presets for react +package_json_path = Rails.root.join("./package.json") +insert_into_file( + package_json_path, + %( "@babel/preset-react",\n), + after: /"presets": \[\n/ +) + +# install rspec-rails +system("bundle add rspec-rails --group development,test") +system("bundle exec rails g rspec:install") + +# copy files +directory( + Rails.root.join("../e2e_template/files"), + Rails.root, + force: true +) diff --git a/spec/generator_specs/generator_spec.rb b/spec/generator_specs/generator_spec.rb new file mode 100644 index 000000000..3ed7a738f --- /dev/null +++ b/spec/generator_specs/generator_spec.rb @@ -0,0 +1,169 @@ +require "pathname" +require "rake" +require "json" +require "shakapacker/utils/misc" +require "shakapacker/utils/version_syntax_converter" + +GEM_ROOT = Pathname.new(File.expand_path("../../..", __FILE__)) +SPEC_PATH = Pathname.new(File.expand_path("../", __FILE__)) +BASE_RAILS_APP_PATH = SPEC_PATH.join("base-rails-app") +TEMP_RAILS_APP_PATH = SPEC_PATH.join("temp-rails-app") + +describe "Generator" do + before :all do + # Don't use --skip-git because we want .gitignore file to exist in the project + sh_in_dir(SPEC_PATH, %( + rails new base-rails-app --skip-javascript --skip-bundle --skip-spring + rm -rf base-rails-app/.git + )) + + Bundler.with_unbundled_env do + sh_in_dir(BASE_RAILS_APP_PATH, %( + gem update bundler + bundle add shakapacker --path "#{GEM_ROOT}" + )) + end + end + + after :all do + Dir.chdir(SPEC_PATH) + FileUtils.rm_rf(BASE_RAILS_APP_PATH) + end + + describe "shakapacker:install" do + context "in a normal Rails project" do + before :all do + sh_in_dir(SPEC_PATH, "cp -r #{BASE_RAILS_APP_PATH} #{TEMP_RAILS_APP_PATH}") + + Bundler.with_unbundled_env do + sh_in_dir(TEMP_RAILS_APP_PATH, "FORCE=true bundle exec rails shakapacker:install") + end + end + + after :all do + Dir.chdir(SPEC_PATH) + FileUtils.rm_rf(TEMP_RAILS_APP_PATH) + end + + it "creates `config/shakapacker.yml`" do + config_file_relative_path = "config/shakapacker.yml" + actual_content = read(path_in_the_app(config_file_relative_path)) + expected_content = read(path_in_the_gem(config_file_relative_path)) + + expect(actual_content).to eq expected_content + end + + it "replaces package.json with template file" do + actual_content = read(path_in_the_app("package.json")) + + expect(actual_content).to match /"name": "app",/ + end + + it "creates webpack config directory and its files" do + expected_files = [ + "webpack.config.js" + ] + + Dir.chdir(path_in_the_app("config/webpack")) do + exisiting_files_in_config_webpack_dir = Dir.glob("*") + expect(exisiting_files_in_config_webpack_dir).to eq expected_files + end + end + + it "adds binstubs" do + expected_binstubs = [] + Dir.chdir(File.join(GEM_ROOT, "lib/install/bin")) do + expected_binstubs = Dir.glob("bin/*") + end + + Dir.chdir(File.join(TEMP_RAILS_APP_PATH, "bin")) do + actual_binstubs = Dir.glob("*") + expect(actual_binstubs).to include(*expected_binstubs) + end + end + + it "modifies .gitignore" do + actual_content = read(path_in_the_app(".gitignore")) + + expect(actual_content).to match ".yarn-integrity" + end + + it 'adds <%= javascript_pack_tag "application" %>' do + actual_content = read(path_in_the_app("app/views/layouts/application.html.erb")) + + expect(actual_content).to match '<%= javascript_pack_tag "application" %>' + end + + it "updates `bin/setup" do + setup_file_content = read(path_in_the_app("bin/setup")) + expect(setup_file_content).to match %r(^\s*system!\(['"]bin/yarn['"]\)) + end + + it "adds relevant shakapacker version in package.json depending on gem version," do + npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION) + + actual_content = read(path_in_the_app("package.json")) + + expect(actual_content).to match /"shakapacker": "#{npm_version}",/ + end + + it "adds Shakapacker peer dependencies to package.json" do + package_json = JSON.parse(File.read(path_in_the_app("package.json"))) + actual_dependencies = package_json["dependencies"]&.keys + + expected_dependencies = %w( + @babel/core + @babel/plugin-transform-runtime + @babel/preset-env + @babel/runtime + babel-loader + compression-webpack-plugin + terser-webpack-plugin + webpack + webpack-assets-manifest + webpack-cli + webpack-merge + ) + + expect(actual_dependencies).to include(*expected_dependencies) + end + + it "adds Shakapacker peer dev dependencies to package.json" do + package_json = JSON.parse(File.read(path_in_the_app("package.json"))) + actual_dev_dependencies = package_json["devDependencies"]&.keys + + expected_dev_dependencies = %w( + webpack-dev-server + ) + + expect(actual_dev_dependencies).to include(*expected_dev_dependencies) + end + + context "with a basic react app setup" do + it "passes the test for rendering react component on the page" do + Bundler.with_unbundled_env do + sh_in_dir(TEMP_RAILS_APP_PATH, "./bin/rails app:template LOCATION=../e2e_template/template.rb") + expect(sh_in_dir(TEMP_RAILS_APP_PATH, "bundle exec rspec")).to be_truthy + end + end + end + end + end + + private + def path_in_the_app(relative_path = nil) + Pathname.new(File.join([TEMP_RAILS_APP_PATH, relative_path].compact)) + end + + def path_in_the_gem(relative_path = nil) + Pathname.new(File.join([GEM_ROOT, "lib/install" , relative_path].compact)) + end + + def read(path) + File.read(path) + end + + def sh_in_dir(dir, *shell_commands) + Shakapacker::Utils::Misc.sh_in_dir(dir, *shell_commands) + end +end