From 7a205cb71f53216147a701a4b60b95ef0860474a Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Fri, 30 Aug 2024 10:40:00 -0400 Subject: [PATCH] [RMS 2] Tailored Select Generator (#129) This PR adds a new generator for the Tailored Select package. It also adds some developer tools around it for using it in tests and with simple form. --------- Co-authored-by: Braden Rich --- Gemfile.lock | 2 +- README.md | 1 + example_rails7/Gemfile | 4 +- example_rails7/Gemfile.lock | 10 ++- lib/generators/rolemodel/all_generator.rb | 1 + .../app/inputs/tailored_select_input.rb | 75 +++++++++++++++++++ .../rolemodel/tailored_select/README.md | 7 ++ .../rolemodel/tailored_select/USAGE | 5 ++ .../tailored_select_generator.rb | 13 ++++ .../testing/rspec/rspec_generator.rb | 3 +- .../rspec/templates/support/helpers.rb | 3 +- .../support/helpers/select_helper.rb | 33 ++++++++ lib/rolemodel_rails/version.rb | 2 +- .../rolemodel/rspec_generator_spec.rb | 21 ++++++ .../tailored_select_generator_spec.rb | 21 ++++++ 15 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 lib/generators/rolemodel/simple_form/templates/app/inputs/tailored_select_input.rb create mode 100644 lib/generators/rolemodel/tailored_select/README.md create mode 100644 lib/generators/rolemodel/tailored_select/USAGE create mode 100644 lib/generators/rolemodel/tailored_select/tailored_select_generator.rb create mode 100644 lib/generators/rolemodel/testing/rspec/templates/support/helpers/select_helper.rb create mode 100644 spec/generators/rolemodel/rspec_generator_spec.rb create mode 100644 spec/generators/rolemodel/tailored_select_generator_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 092990e4..bfddc0f9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rolemodel_rails (0.9.0) + rolemodel_rails (0.10.0) GEM remote: https://rubygems.org/ diff --git a/README.md b/README.md index 4d291559..24f3d8c5 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ bin/rails g * [Kaminari](./lib/generators/rolemodel/kaminari) * [GoodJob](./lib/generators/rolemodel/good_job) * [Editors](./lib/generators/rolemodel/editors) +* [Tailored Select](./lib/generators/rolemodel/tailored_select) ## Development diff --git a/example_rails7/Gemfile b/example_rails7/Gemfile index 7f04509d..651945e1 100644 --- a/example_rails7/Gemfile +++ b/example_rails7/Gemfile @@ -1,7 +1,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby "3.2.2" +ruby "3.3.0" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.0.5" @@ -68,4 +68,4 @@ group :test do gem "webdrivers" end -gem 'rolemodel_rails', '~> 0.8.0', group: :development, path: '..' +gem 'rolemodel_rails', '~> 0.10.0', group: :development, path: '..' diff --git a/example_rails7/Gemfile.lock b/example_rails7/Gemfile.lock index 96e8dd0d..af08de28 100644 --- a/example_rails7/Gemfile.lock +++ b/example_rails7/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - rolemodel_rails (0.8.0) + rolemodel_rails (0.10.0) GEM remote: https://rubygems.org/ @@ -114,6 +114,7 @@ GEM matrix (0.4.2) method_source (1.0.0) mini_mime (1.1.2) + mini_portile2 (2.8.7) minitest (5.18.0) msgpack (1.7.1) net-imap (0.3.6) @@ -126,7 +127,8 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.9) - nokogiri (1.15.2-arm64-darwin) + nokogiri (1.15.2) + mini_portile2 (~> 2.8.2) racc (~> 1.4) pg (1.5.3) public_suffix (5.0.1) @@ -221,7 +223,7 @@ DEPENDENCIES puma (~> 5.0) rails (~> 7.0.5) redis (~> 4.0) - rolemodel_rails (~> 0.8.0)! + rolemodel_rails (~> 0.10.0)! selenium-webdriver sprockets-rails stimulus-rails @@ -231,7 +233,7 @@ DEPENDENCIES webdrivers RUBY VERSION - ruby 3.2.2p53 + ruby 3.3.0p0 BUNDLED WITH 2.4.14 diff --git a/lib/generators/rolemodel/all_generator.rb b/lib/generators/rolemodel/all_generator.rb index db564762..09d059c1 100644 --- a/lib/generators/rolemodel/all_generator.rb +++ b/lib/generators/rolemodel/all_generator.rb @@ -22,6 +22,7 @@ def run_all_the_generators generate 'rolemodel:good_job' generate 'rolemodel:kaminari' generate 'rolemodel:editors' + generate 'rolemodel:tailored_select' end end end diff --git a/lib/generators/rolemodel/simple_form/templates/app/inputs/tailored_select_input.rb b/lib/generators/rolemodel/simple_form/templates/app/inputs/tailored_select_input.rb new file mode 100644 index 00000000..5bef4a9c --- /dev/null +++ b/lib/generators/rolemodel/simple_form/templates/app/inputs/tailored_select_input.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# TailoredSelectInput is a custom input type for SimpleForm that renders a tailored select web component +# +# Options: +# Any options available for SimpleForm's CollectionSelectInput (A.K.A a normal HTML select) +# +# Usage: +# <%= f.input :my_field, as: :tailored_select %> + +module ActionView + module Helpers + class FormBuilder + def tailored_select(method, collection, value_method, text_method, options = {}, html_options = {}) + @template.tailored_select(@object_name, method, collection, value_method, text_method, + objectify_options(options), @default_html_options.merge(html_options)) + end + end + + module FormOptionsHelper + def tailored_select(object, method, collection, value_method, text_method, options = {}, html_options = {}) + Tags::TailoredSelect.new(object, method, self, collection, value_method, text_method, options, + html_options).render + end + end + + module Tags + class TailoredSelect < CollectionSelect + private + + def select_content_tag(option_tags, options, html_options) + html_options = html_options.stringify_keys + %i[required multiple size].each do |prop| + html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s) + end + + add_default_name_and_id(html_options) + + if placeholder_required?(html_options) + if options[:include_blank] == false + raise ArgumentError, + 'include_blank cannot be false for a required field.' + end + + options[:include_blank] ||= true unless options[:prompt] + end + + value = options.fetch(:selected) { value() } + # For real, this is the only line that changed from CollectionSelect, just changed the tag name + select = content_tag('tailored-select', add_options(option_tags, options, value), html_options) + + if html_options['multiple'] && options.fetch(:include_hidden, true) + tag('input', disabled: html_options['disabled'], name: html_options['name'], type: 'hidden', value: '', + autocomplete: 'off') + select + else + select + end + end + end + end + end +end + +class TailoredSelectInput < SimpleForm::Inputs::CollectionSelectInput + def input(wrapper_options = nil) + @builder.tailored_select( + attribute_name, collection, *detect_collection_methods.reverse, input_options, + merge_wrapper_options(input_html_options, wrapper_options) + ) + end + + def input_html_classes + [] + end +end diff --git a/lib/generators/rolemodel/tailored_select/README.md b/lib/generators/rolemodel/tailored_select/README.md new file mode 100644 index 00000000..20a7d26d --- /dev/null +++ b/lib/generators/rolemodel/tailored_select/README.md @@ -0,0 +1,7 @@ +# TailoredSelect Generator + +`rails g rolemodel:tailored_select` + +## What you get + +The [Tailored Select](https://github.com/RoleModel/tailored-select) web component diff --git a/lib/generators/rolemodel/tailored_select/USAGE b/lib/generators/rolemodel/tailored_select/USAGE new file mode 100644 index 00000000..57754b53 --- /dev/null +++ b/lib/generators/rolemodel/tailored_select/USAGE @@ -0,0 +1,5 @@ +Description: + runs the tailored select generator + +Example: + rails generate rolemodel:tailored_select diff --git a/lib/generators/rolemodel/tailored_select/tailored_select_generator.rb b/lib/generators/rolemodel/tailored_select/tailored_select_generator.rb new file mode 100644 index 00000000..019dad8a --- /dev/null +++ b/lib/generators/rolemodel/tailored_select/tailored_select_generator.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Rolemodel + class TailoredSelectGenerator < Rails::Generators::Base + source_root File.expand_path('templates', __dir__) + + def add_tailored_select_package + say 'Installing Tailored Select package', :green + + run 'yarn add @rolemodel/tailored-select' + end + end +end diff --git a/lib/generators/rolemodel/testing/rspec/rspec_generator.rb b/lib/generators/rolemodel/testing/rspec/rspec_generator.rb index 2f04ceef..07bc32b6 100644 --- a/lib/generators/rolemodel/testing/rspec/rspec_generator.rb +++ b/lib/generators/rolemodel/testing/rspec/rspec_generator.rb @@ -25,8 +25,9 @@ def add_spec_files template '.rspec', '.rspec' template 'support/capybara_drivers.rb', 'spec/support/capybara_drivers.rb' template 'support/capybara_testid.rb', 'spec/support/capybara_testid.rb' - template 'support/helpers/test_element_helper.rb', 'spec/support/helpers/test_element_helper.rb' template 'support/helpers/action_cable_helper.rb', 'spec/support/helpers/action_cable_helper.rb' + template 'support/helpers/select_helper.rb', 'spec/support/helpers/select_helper.rb' + template 'support/helpers/test_element_helper.rb', 'spec/support/helpers/test_element_helper.rb' template 'support/helpers.rb', 'spec/support/helpers.rb' template 'support/webpacker.rb', 'spec/support/webpacker.rb' append_file '.gitignore', 'spec/examples.txt' diff --git a/lib/generators/rolemodel/testing/rspec/templates/support/helpers.rb b/lib/generators/rolemodel/testing/rspec/templates/support/helpers.rb index de0ba4aa..d0c27016 100644 --- a/lib/generators/rolemodel/testing/rspec/templates/support/helpers.rb +++ b/lib/generators/rolemodel/testing/rspec/templates/support/helpers.rb @@ -4,6 +4,7 @@ RSpec.configure do |c| # for example, given you have a spec/support/helpers/login_helpers.rb - c.include TestElementHelper, type: :system c.include ActionCableHelper, type: :system + c.include SelectHelper, type: :system + c.include TestElementHelper, type: :system end diff --git a/lib/generators/rolemodel/testing/rspec/templates/support/helpers/select_helper.rb b/lib/generators/rolemodel/testing/rspec/templates/support/helpers/select_helper.rb new file mode 100644 index 00000000..42d968e7 --- /dev/null +++ b/lib/generators/rolemodel/testing/rspec/templates/support/helpers/select_helper.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module SelectHelper + include Capybara::DSL + + def smart_select(value, from:) + label = find(:label, text: from, match: :first) + input = find(id: label['for'], visible: :all) + + if input.tag_name == 'input' && input.native.attribute(:'aria-controls').include?('ts-dropdown') + tom_select(input, value) + elsif input.tag_name == 'tailored-select' + tailored_select(input, value) + else + regular_select(value.to_s, from:) + end + end + + def tailored_select(input, value) + option = input.find(:option, value, visible: :all) + execute_script('arguments[0].click();', option) + end + + def tom_select(input, value) + input.ancestor('.ts-control').click + input.send_keys(value) + sleep 0.5 # required due to bug in tom-select + input.send_keys(:enter) + end + + alias regular_select select + alias select smart_select +end diff --git a/lib/rolemodel_rails/version.rb b/lib/rolemodel_rails/version.rb index b73d23e5..866ddd80 100644 --- a/lib/rolemodel_rails/version.rb +++ b/lib/rolemodel_rails/version.rb @@ -1,3 +1,3 @@ module RolemodelRails - VERSION = '0.9.0' + VERSION = '0.10.0' end diff --git a/spec/generators/rolemodel/rspec_generator_spec.rb b/spec/generators/rolemodel/rspec_generator_spec.rb new file mode 100644 index 00000000..5d5cd42e --- /dev/null +++ b/spec/generators/rolemodel/rspec_generator_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' +require 'generators/rolemodel/testing/rspec/rspec_generator' + +RSpec.describe Rolemodel::Testing::RspecGenerator, type: :generator do + destination File.expand_path('tmp/', File.dirname(__FILE__)) + + before(:all) do + prepare_test_app + run_generator + end + + after(:all) do + cleanup_test_app + end + + it 'adds the correct helpers' do + assert_file 'spec/support/helpers/action_cable_helper.rb' + assert_file 'spec/support/helpers/select_helper.rb' + assert_file 'spec/support/helpers/test_element_helper.rb' + end +end diff --git a/spec/generators/rolemodel/tailored_select_generator_spec.rb b/spec/generators/rolemodel/tailored_select_generator_spec.rb new file mode 100644 index 00000000..a5c8f0ea --- /dev/null +++ b/spec/generators/rolemodel/tailored_select_generator_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' +require 'generators/rolemodel/tailored_select/tailored_select_generator' + +RSpec.describe Rolemodel::TailoredSelectGenerator, type: :generator do + destination File.expand_path('tmp/', File.dirname(__FILE__)) + + before(:all) do + prepare_test_app + FileUtils.cd(destination_root) { run_generator } + end + + after(:all) do + cleanup_test_app + end + + it 'adds tailored select to package.json' do + assert_file 'package.json' do |content| + expect(content).to include('"@rolemodel/tailored-select":') + end + end +end