inline_forms_installer 8.1.20 → 8.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/inline_forms_installer.gemspec +9 -9
  3. data/lib/inline_forms_installer/creator.rb +8 -7
  4. data/lib/inline_forms_installer/installer_core.rb +130 -42
  5. data/lib/inline_forms_installer/user_model_config.rb +2 -1
  6. data/lib/inline_forms_installer/version.rb +2 -1
  7. data/lib/inline_forms_installer.rb +1 -0
  8. data/lib/installer_templates/example_app_assets/seed_images/apt1_bedroom.png +0 -0
  9. data/lib/installer_templates/example_app_assets/seed_images/apt1_kitchen.png +0 -0
  10. data/lib/installer_templates/example_app_assets/seed_images/apt1_living.png +0 -0
  11. data/lib/installer_templates/example_app_assets/seed_images/apt2_kitchen.png +0 -0
  12. data/lib/installer_templates/example_app_assets/seed_images/apt2_living.png +0 -0
  13. data/lib/installer_templates/example_app_assets/seed_images/apt2_terrace.png +0 -0
  14. data/lib/installer_templates/example_app_assets/seed_images/apt3_living.png +0 -0
  15. data/lib/installer_templates/example_app_assets/seed_images/apt3_loft.png +0 -0
  16. data/lib/installer_templates/example_app_assets/seed_images/apt3_view.png +0 -0
  17. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_opening_date_test.rb +7 -6
  18. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +28 -0
  19. data/lib/installer_templates/example_app_tests/test/integration/example_app_photo_comments_test.rb +90 -0
  20. data/lib/installer_templates/example_app_tests/test/integration/example_app_production_ssl_test.rb +19 -0
  21. data/lib/installer_templates/example_app_tests/test/integration/example_app_showcase_date_time_fields_test.rb +69 -6
  22. data/lib/installer_templates/example_app_tests/test/integration/example_app_showcase_gallery_test.rb +68 -0
  23. data/lib/installer_templates/example_app_tests/test/integration/example_app_user_locale_test.rb +46 -0
  24. data/lib/installer_templates/example_app_tests/test/integration/example_app_user_theme_test.rb +76 -0
  25. data/lib/installer_templates/example_app_views/owners/_owner_tabs.html.erb +3 -3
  26. metadata +15 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2acd92d33de3e1bcd824630a6a4e5166906d938ffb4baa18e8580cb9c8df63b
4
- data.tar.gz: e26ce5fb20d87ac207540afa979ab716f2d5713663b3608eceb0b7185d1fab8f
3
+ metadata.gz: 94ac8df294cd719110b2360768007fbfb8c26c95749c95323a0e61b969801b4e
4
+ data.tar.gz: b5c245794ab67879467b227eef31fb523927f5c8f27634bb48f173222246da0c
5
5
  SHA512:
6
- metadata.gz: 40cd451d291f9920a74d09f4f7e2b61166438413b06e2c73d51463d84ddce674ac7ed88753f6276503953e0c99792761c9cfcdbd09a6290e64fcd18b47099084
7
- data.tar.gz: 5a87788bf2d7c3878102d7e973f63565c12532de1677043e39c4bee61472c4ea710362ebbebc177b544242db56e9e9a0e5c8673c83a1fa9acd1216d8afcf31cd
6
+ metadata.gz: c8e07fcf3fa59b3a0c0a8b548ec06f78032e9659410351abe5ae13f2d03e2b8f66e4d3321f2111367dd9aec31daaa8263b1eeabfb79d2736fc2fb0745652ff12
7
+ data.tar.gz: 866e3d7586e1e03b3d19e5023f834f8665794d436e17b7a6d58f95a8c1aa095c3cff58687d34b18cd77be65fc875d24517a4c130d1792ee7a2c895341b903d9a
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
+
2
3
  $:.push File.expand_path("../lib", __FILE__)
3
4
  require "inline_forms/gem_files"
4
5
  require "inline_forms_installer/version"
@@ -7,17 +8,17 @@ Gem::Specification.new do |s|
7
8
  s.name = "inline_forms_installer"
8
9
  s.version = InlineFormsInstaller::VERSION
9
10
  s.platform = Gem::Platform::RUBY
10
- s.authors = ["Ace Suares", "Lemuel Boyce", "Manuel Ortega"]
11
- s.email = ["ace@suares.com"]
12
- s.homepage = %q{http://github.com/acesuares/inline_forms}
13
- s.summary = %q{CLI and Rails app template for generating inline_forms applications.}
14
- s.description = %q{Installs the `inline_forms` CLI and scaffolds opinionated Rails apps with Devise, CanCan, PaperTrail, and optional example data.}
15
- s.licenses = ["MIT"]
11
+ s.authors = [ "Ace Suares", "Lemuel Boyce", "Manuel Ortega" ]
12
+ s.email = [ "ace@suares.com" ]
13
+ s.homepage = %q(http://github.com/acesuares/inline_forms)
14
+ s.summary = %q(CLI and Rails app template for generating inline_forms applications.)
15
+ s.description = %q(Installs the `inline_forms` CLI and scaffolds opinionated Rails apps with Devise, CanCan, PaperTrail, and optional example data.)
16
+ s.licenses = [ "MIT" ]
16
17
  s.required_ruby_version = ">= 4.0.0"
17
18
 
18
19
  s.files = InlineFormsGemFiles.gem_files(include_installer: true)
19
- s.executables = ["inline_forms"]
20
- s.require_paths = ["lib"]
20
+ s.executables = [ "inline_forms" ]
21
+ s.require_paths = [ "lib" ]
21
22
 
22
23
  s.add_dependency("inline_forms", "~> 8")
23
24
  s.add_dependency("thor", ">= 1.0", "< 2.0")
@@ -27,5 +28,4 @@ Gem::Specification.new do |s|
27
28
  # otherwise it installs without RVM (any version manager, or none, works —
28
29
  # Bundler isolates deps per app via Gemfile.lock). Install `gem install rvm`
29
30
  # to opt in to gemset integration, or pass `--no-rvm` to skip it explicitly.
30
-
31
31
  end
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+
2
3
  require "securerandom"
3
4
  require "thor"
4
5
  require_relative "create_log"
@@ -16,13 +17,13 @@ module InlineFormsInstaller
16
17
  end
17
18
 
18
19
  desc "create APP", "create an application with inline_forms v#{InlineFormsInstaller.inline_forms_version}"
19
- DATABASE_OPTIONS = %w(sqlite mysql)
20
- method_option :database, :aliases => "-d", :banner => DATABASE_OPTIONS.join("|"), :desc => "specify development database"
21
- method_option :example, :type => :boolean, :desc => "install the example app. uses sqlite as development database"
22
- method_option :email, :aliases => "-e", :default => "admin@example.com", :desc => "specify admin email"
23
- method_option :password, :aliases => "-p", :default => "admin999", :desc => "specify admin password"
24
- method_option :skiprvm, :aliases => "--no-rvm", :type => :boolean, :default => false, :desc => "install inline_forms without RVM"
25
- method_option :user_model, :aliases => "-U", :default => "User", :banner => "CLASS", :desc => "Devise model class (e.g. Member); Warden scope stays :user (current_user)"
20
+ DATABASE_OPTIONS = %w[sqlite mysql]
21
+ method_option :database, aliases: "-d", banner: DATABASE_OPTIONS.join("|"), desc: "specify development database"
22
+ method_option :example, type: :boolean, desc: "install the example app. uses sqlite as development database"
23
+ method_option :email, aliases: "-e", default: "admin@example.com", desc: "specify admin email"
24
+ method_option :password, aliases: "-p", default: "admin999", desc: "specify admin password"
25
+ method_option :skiprvm, aliases: "--no-rvm", type: :boolean, default: false, desc: "install inline_forms without RVM"
26
+ method_option :user_model, aliases: "-U", default: "User", banner: "CLASS", desc: "Devise model class (e.g. Member); Warden scope stays :user (current_user)"
26
27
 
27
28
  def create(app_name)
28
29
  def self.skiprvm
@@ -103,7 +103,7 @@ end
103
103
 
104
104
  add_source 'https://rubygems.org'
105
105
 
106
- gem 'cancancan'
106
+ gem 'cancancan', '~> 3.6'
107
107
  gem 'carrierwave', '~> 3.1'
108
108
  gem 'devise', '~> 5.0'
109
109
  gem 'devise-i18n', '~> 1.16'
@@ -119,12 +119,11 @@ if ENV["INLINE_FORMS_GEMFILE_PATH"] && File.directory?(ENV["INLINE_FORMS_GEMFILE
119
119
  else
120
120
  gem "inline_forms", "~> 8"
121
121
  end
122
+ # jQuery is required by Foundation 6's JS only. jQuery UI is fully gone since
123
+ # 8.1.26 (native date/time/month inputs in 8.1.25; datalist combobox + range
124
+ # slider in 8.1.26), as are jquery-timepicker-rails, jquery-ui-rails and
125
+ # rails-jquery-autocomplete.
122
126
  gem 'jquery-rails'
123
- gem 'jquery-timepicker-rails'
124
- # jQuery UI JavaScript (`//= require jquery.ui.all` in inline_forms.js). SCSS + PNGs
125
- # are vendored in the inline_forms engine (Dart Sass cannot evaluate sass-rails
126
- # `image-path()`). Pin matches former jquery-ui-sass-rails 4.0.3.x stack.
127
- gem 'jquery-ui-rails', '4.0.3'
128
127
  # Foundation Icons SCSS + fonts are vendored in the inline_forms engine (Dart Sass;
129
128
  # foundation-icons-sass-rails depended on sass-rails).
130
129
  gem 'mini_magick'
@@ -138,7 +137,6 @@ gem 'money-rails', '~> 3.0'
138
137
  gem 'mysql2'
139
138
  gem 'paper_trail', '~> 17.0'
140
139
  gem 'rails-i18n', '~> 8.1'
141
- gem 'rails-jquery-autocomplete'
142
140
  gem 'rails', '~> 8.1'
143
141
  gem 'rake'
144
142
  # NOTE: the `rvm` gem is intentionally NOT a runtime dependency of generated
@@ -156,10 +154,11 @@ gem 'importmap-rails'
156
154
  # use `<turbo-frame>` + HTML responses (see docs/ujs-to-turbo.md). Registers the
157
155
  # `turbo_stream` MIME type for optional stream responses.
158
156
  gem 'turbo-rails'
159
- gem 'tabs_on_rails', '~> 3.0'
157
+ # tabs (set_tab / tabs_tag) are vendored in the inline_forms engine since
158
+ # 8.1.23 (InlineForms::Tabs) — the unmaintained tabs_on_rails gem is gone.
160
159
  gem 'unicorn'
161
160
  gem 'validation_hints', '~> 8'
162
- gem 'will_paginate'
161
+ gem 'will_paginate', '~> 4.0'
163
162
 
164
163
  gem_group :test do
165
164
  # Rails 7 still expects Minitest 5; 6.x breaks the railties test runner.
@@ -353,6 +352,13 @@ class #{user_cfg.devise_migration_class} < ActiveRecord::Migration[8.1]
353
352
 
354
353
  t.string :name
355
354
  t.integer :locale_id
355
+ # Preset UI theme (inline_forms 8.1.29+): 0 default, 1 dark, 2 sepia,
356
+ # 3 high-contrast — see #{user_cfg.class_name}::INLINE_FORMS_THEMES.
357
+ t.integer :theme, default: 0, null: false
358
+ # Per-user color overrides (inline_forms 8.1.32+, theming Pattern 2):
359
+ # hex "#rrggbb" or NULL; rendered as --if-color-* overrides in <head>.
360
+ t.string :primary_color
361
+ t.string :accent_color
356
362
 
357
363
  t.timestamps
358
364
  end
@@ -396,6 +402,10 @@ create_file user_cfg.model_path, <<-USER_MODEL.strip_heredoc
396
402
 
397
403
  # validations
398
404
  validates :name, :presence => true
405
+ # Theming Pattern 2 color overrides; the color_field element only ever
406
+ # submits "#rrggbb" or nil, but validate anyway (console edits, imports).
407
+ validates :primary_color, format: { with: /\\A#\\h{6}\\z/i }, allow_blank: true
408
+ validates :accent_color, format: { with: /\\A#\\h{6}\\z/i }, allow_blank: true
399
409
 
400
410
  # Default ordering for inline_forms list views (and any explicit caller
401
411
  # via `#{user_cfg.class_name}.inline_forms_list`). Avoids `default_scope`
@@ -415,12 +425,32 @@ create_file user_cfg.model_path, <<-USER_MODEL.strip_heredoc
415
425
  return !!self.roles.find_by_name(role)
416
426
  end
417
427
 
428
+ # Preset UI themes (inline_forms Pattern 1 theming). The integer `theme`
429
+ # column maps to a body class `theme-<name>`; the palettes ship in the
430
+ # inline_forms engine (_theme.scss). The engine layout calls
431
+ # #inline_forms_theme on current_user when the method exists.
432
+ INLINE_FORMS_THEMES = { 0 => 'default', 1 => 'dark', 2 => 'sepia', 3 => 'high-contrast' }.freeze
433
+
434
+ def inline_forms_theme
435
+ INLINE_FORMS_THEMES.fetch(theme.to_i, 'default')
436
+ end
437
+
438
+ # Theming Pattern 2: --if-color-* overrides for this user, rendered as an
439
+ # inline <style> by the engine layout (inline_forms_user_color_overrides_style).
440
+ # Keys are --if-color- suffixes; values must be "#rrggbb".
441
+ def inline_forms_color_overrides
442
+ { 'primary' => primary_color, 'accent' => accent_color }.compact
443
+ end
444
+
418
445
  def inline_forms_attribute_list
419
446
  @inline_forms_attribute_list ||= [
420
447
  [ :header_user_login, :header ],
421
448
  [ :name, :text_field ],
422
449
  [ :email, :text_field ],
423
450
  [ :locale, :dropdown ],
451
+ [ :theme, :dropdown_with_values, { 0 => 'default', 1 => 'dark', 2 => 'sepia', 3 => 'high contrast' } ],
452
+ [ :primary_color, :color_field ],
453
+ [ :accent_color, :color_field ],
424
454
  [ :password, :devise_password_field ],
425
455
  [ :header_user_roles, :header ],
426
456
  [ :roles, :check_list ],
@@ -654,24 +684,36 @@ create_file "app/controllers/application_controller.rb", <<-END_APPCONTROLLER.st
654
684
  end
655
685
  # Comment previous lines if you don't want Devise authentication
656
686
 
657
- # Uncomment next line if you want I18n (based on subdomain)
658
- # before_action :set_locale
659
-
660
- # Uncomment next line and specify default locale
661
- # I18n.default_locale = :en
687
+ # Per-user locale (inline_forms 8.1.31): the signed-in user's Locale
688
+ # record (its `name`: en/nl/...) drives I18n for the request. Unknown or
689
+ # blank names fall back to I18n.default_locale, and I18n.with_locale
690
+ # restores the previous locale afterwards, so nothing leaks across
691
+ # requests or threads. Users pick their locale on their own user panel
692
+ # (the [:locale, :dropdown] row).
693
+ around_action :switch_locale
694
+
695
+ def switch_locale(&action)
696
+ candidate = current_user&.locale&.name.to_s.strip
697
+ locale =
698
+ if I18n.available_locales.map(&:to_s).include?(candidate)
699
+ candidate
700
+ else
701
+ I18n.default_locale
702
+ end
703
+ I18n.with_locale(locale, &action)
704
+ end
662
705
 
663
- # Uncomment next line and specify available locales
664
- # I18n.available_locales = [ :en, :nl, :pp ]
706
+ # Restrict/extend the locale whitelist in config/application.rb:
707
+ # config.i18n.available_locales = [:en, :nl]
708
+ # config.i18n.default_locale = :en
665
709
 
666
- # Uncomment next nine line if you want locale based on subdomain, like 'it.example.com, de.example.com'
667
- # def set_locale
668
- # I18n.locale = extract_locale_from_subdomain || I18n.default_locale
669
- # end
670
- #
671
- # def extract_locale_from_subdomain
710
+ # Uncomment if you prefer locale based on subdomain ('nl.example.com')
711
+ # instead of the per-user setting above:
712
+ # around_action :switch_locale_from_subdomain
713
+ # def switch_locale_from_subdomain(&action)
672
714
  # locale = request.subdomains.first
673
- # return nil if locale.nil?
674
- # I18n.available_locales.include?(locale.to_sym) ? locale.to_s : nil
715
+ # locale = nil unless locale && I18n.available_locales.map(&:to_s).include?(locale)
716
+ # I18n.with_locale(locale || I18n.default_locale, &action)
675
717
  # end
676
718
  end
677
719
  END_APPCONTROLLER
@@ -698,6 +740,19 @@ create_file "app/models/ability.rb", <<-END_ABILITY.strip_heredoc
698
740
  end
699
741
  END_ABILITY
700
742
 
743
+ # SSL in production. `rails new` only enables these when Kamal is skipped
744
+ # (its proxy config takes over otherwise), but generated apps deploy via the
745
+ # shipped Capistrano/Unicorn setup behind an SSL-terminating reverse proxy —
746
+ # and the Devise mailer config below already assumes https. Enabling both
747
+ # matches the Rails 8 defaults and closes Brakeman's ForceSSL warning
748
+ # (full-gate CI scans the generated app). Serving plain HTTP internally?
749
+ # Re-comment these in your app's config/environments/production.rb.
750
+ say "- Enabling assume_ssl/force_ssl in environments/production.rb..."
751
+ gsub_file "config/environments/production.rb",
752
+ "# config.assume_ssl = true", "config.assume_ssl = true"
753
+ gsub_file "config/environments/production.rb",
754
+ "# config.force_ssl = true", "config.force_ssl = true"
755
+
701
756
  # devise mailer stuff
702
757
  say "- Injecting devise mailer stuff in environments/production.rb..."
703
758
  # strip_heredoc_with_indent(2) became strip_heredoc(2), but only in rails 4... :-(
@@ -785,8 +840,16 @@ git commit: " -a -m 'Initial Commit'"
785
840
  # example
786
841
  if ENV['install_example'] == 'true'
787
842
  say "\nInstalling example application..."
788
- run 'bundle exec rails g inline_forms Photo name:string caption:string image:image_field description:rich_text apartment:belongs_to _list_order:name _presentation:\'#{name}\''
843
+ run 'bundle exec rails g inline_forms Photo name:string caption:string image:image_field description:rich_text apartment:belongs_to comments:has_many comments:associated _list_order:name _presentation:\'#{name}\''
789
844
  run 'bundle exec rails generate uploader Image'
845
+
846
+ # Comment: second nesting level (Apartment -> Photo -> Comment) so the
847
+ # example app demonstrates :associated panels inside an opened nested row.
848
+ # Like Photo it has no `_enabled`, so it is not accessible through
849
+ # top-level HTML — only through the comments panel on an opened photo.
850
+ say "- Generating Comment model (nested under Photo -- nesting-in-nesting demo)..."
851
+ sleep 1
852
+ run 'bundle exec rails g inline_forms Comment body:text_area photo:belongs_to _list_order:body _presentation:\'#{body}\''
790
853
  run 'bundle exec rails g inline_forms Apartment name:string title:string opening_date:date description:rich_text photos:has_many photos:associated _enabled:yes _list_order:name _list_search:name _presentation:\'#{name}\''
791
854
 
792
855
  say "- Apartment name is required..."
@@ -861,23 +924,18 @@ if ENV['install_example'] == 'true'
861
924
 
862
925
  run 'bundle exec rake db:migrate'
863
926
 
864
- # Seed the photos gallery from a local `pics/` folder. The folder is
865
- # *gitignored* in the gem source (so the built .gem stays small and
866
- # the gallery images are not committed) which means INSTALLER_ROOT/pics
867
- # exists only when the installer is run from the source repo, not when
868
- # it is run from an installed gem on the developer's box. We therefore
869
- # check, in order:
870
- # 1. ENV['INLINE_FORMS_SEED_PICS'] -- explicit override path
871
- # 2. INSTALLER_ROOT/pics -- monorepo / installer checkout
872
- # 3. /home/code/inline_forms/pics -- local dev convention
873
- # and copy whichever is found into the generated app's db/seed_images/.
874
- # The migration generated below is what reads from db/seed_images at
875
- # `db:migrate` / `db:test:prepare` time, so this copy is only ever a
876
- # one-shot at app generation.
927
+ # Seed the photos gallery. The 9 CC0 placeholder images (solid pastel +
928
+ # "Apt N / Room" label, ~4 KB each, generated with ImageMagick — see
929
+ # stuff/2026-07-06-forgejo-ci-rubocop-brakeman.md) ship with the installer
930
+ # under lib/installer_templates/example_app_assets/seed_images/, so every
931
+ # install laptop, CI container, anywhere gets the same gallery. Before
932
+ # 8.1.35 this looked for a *gitignored* local pics/ folder, which existed
933
+ # only on the maintainer's box: gem installs elsewhere (e.g. the Forgejo
934
+ # full-gate runner) silently produced zero photos and 12 test failures.
935
+ # ENV['INLINE_FORMS_SEED_PICS'] still overrides with your own folder.
877
936
  pics_candidates = [
878
937
  ENV["INLINE_FORMS_SEED_PICS"],
879
- File.join(INSTALLER_ROOT, "pics"),
880
- "/home/code/inline_forms/pics",
938
+ File.join(INSTALLER_ROOT, "lib/installer_templates/example_app_assets/seed_images"),
881
939
  ].compact
882
940
  pics_src = pics_candidates.find { |p| Dir.exist?(p) }
883
941
  if pics_src
@@ -1043,6 +1101,13 @@ if ENV['install_example'] == 'true'
1043
1101
  apartments: [] },
1044
1102
  ].freeze
1045
1103
 
1104
+ # Comments seeded on each apartment's first photo (Apartment ->
1105
+ # Photo -> Comment: the second nesting level).
1106
+ COMMENTS = [
1107
+ "Lovely light in this room!",
1108
+ "Could use a fresh coat of paint.",
1109
+ ].freeze
1110
+
1046
1111
  def up
1047
1112
  seed_dir = Rails.root.join("db", "seed_images")
1048
1113
  all_pics = seed_dir.directory? ?
@@ -1073,6 +1138,15 @@ if ENV['install_example'] == 'true'
1073
1138
  end
1074
1139
  end
1075
1140
 
1141
+ # Nesting-in-nesting demo data: two comments on each apartment's
1142
+ # first photo, so the comments panel inside an opened photo row
1143
+ # is visibly populated out of the box.
1144
+ if (first_photo = apt.photos.order(:id).first)
1145
+ COMMENTS.each do |body|
1146
+ Comment.find_or_create_by!(photo: first_photo, body: body)
1147
+ end
1148
+ end
1149
+
1076
1150
  apt_records[spec[:name]] = apt
1077
1151
  end
1078
1152
 
@@ -1099,6 +1173,7 @@ if ENV['install_example'] == 'true'
1099
1173
  APARTMENTS.each do |spec|
1100
1174
  apt = Apartment.find_by(name: spec[:name])
1101
1175
  next unless apt
1176
+ apt.photos.each { |photo| photo.comments.destroy_all }
1102
1177
  apt.photos.destroy_all
1103
1178
  apt.destroy
1104
1179
  end
@@ -1119,7 +1194,7 @@ if ENV['install_example'] == 'true'
1119
1194
  # ---------------------------------------------------------------------
1120
1195
  say "- Generating FormElementShowcase (one resource per kept Tier 1 form_element)..."
1121
1196
  sleep 1
1122
- run %q{bundle exec rails g inline_forms FormElementShowcase title:string body_plain_area:plain_text_area count:integer_field price:decimal_field amount:money_field latitude:decimal_field{9,6} longitude:decimal_field{10,6} meeting_date:date_select meeting_time:time_select birth_month:month_select start_month:month_year_picker is_active:check_box gender:radio_button rating_int:dropdown_with_integers priority:dropdown_with_values priority2:dropdown_with_values stars:dropdown_with_values_with_stars scale_int:scale_with_integers scale_val:scale_with_values attachment:file_field jingle:audio_field cover:image_field description:rich_text locales:has_and_belongs_to_many _enabled:yes _list_order:title _list_search:title _presentation:'#{title}'}
1197
+ run %q{bundle exec rails g inline_forms FormElementShowcase title:string body_plain_area:plain_text_area count:integer_field price:decimal_field amount:money_field latitude:decimal_field{9,6} longitude:decimal_field{10,6} meeting_date:date_select meeting_time:time_select birth_month:month_select start_month:month_year_picker is_active:check_box gender:radio_button rating_int:dropdown_with_integers priority:dropdown_with_values priority2:dropdown_with_values stars:dropdown_with_values_with_stars scale_int:scale_with_integers scale_val:scale_with_values attachment:file_field jingle:audio_field cover:image_field gallery:multi_image_field description:rich_text locales:has_and_belongs_to_many _enabled:yes _list_order:title _list_search:title _presentation:'#{title}'}
1123
1198
 
1124
1199
  say "- Generating Attachment + Jingle uploaders (Cover reuses ImageUploader)..."
1125
1200
  run "bundle exec rails generate uploader Attachment"
@@ -1150,6 +1225,9 @@ if ENV['install_example'] == 'true'
1150
1225
  showcase_migration = Dir.glob("db/migrate/*_inline_forms_create_form_element_showcases.rb").first
1151
1226
  if showcase_migration
1152
1227
  gsub_file showcase_migration, /t\.integer :amount\b/, "t.integer :amount_cents, default: 0, null: false"
1228
+ # multi_image_field stores a JSON array of filenames (mount_uploaders);
1229
+ # the registry maps it to :string, widen to :text for the serialized list.
1230
+ gsub_file showcase_migration, /t\.string :gallery\b/, "t.text :gallery"
1153
1231
  end
1154
1232
 
1155
1233
  create_file "config/initializers/money.rb", <<-MONEY_INIT.strip_heredoc
@@ -1185,7 +1263,9 @@ if ENV['install_example'] == 'true'
1185
1263
  " validates :price, numericality: true, allow_blank: true\n" \
1186
1264
  " validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_blank: true\n" \
1187
1265
  " validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_blank: true\n" \
1188
- " mount_uploader :attachment, AttachmentUploader\n monetize :amount_cents\n\n def locales_display\n locales\n end\n",
1266
+ " mount_uploader :attachment, AttachmentUploader\n" \
1267
+ " serialize :gallery, coder: JSON\n mount_uploaders :gallery, ImageUploader\n" \
1268
+ " monetize :amount_cents\n\n def locales_display\n locales\n end\n",
1189
1269
  after: "class FormElementShowcase < ApplicationRecord\n"
1190
1270
 
1191
1271
  # Value-bearing rows for every form_element that needs a values hash
@@ -1272,6 +1352,7 @@ if ENV['install_example'] == 'true'
1272
1352
  attachment: Attachment
1273
1353
  jingle: Jingle
1274
1354
  cover: Cover
1355
+ gallery: Gallery (multiple images)
1275
1356
  description: Description
1276
1357
  locales: Locales (editable)
1277
1358
  locales_display: Locales (read-only)
@@ -1382,6 +1463,13 @@ if ENV['install_example'] == 'true'
1382
1463
  next if full.public_send(attr).present?
1383
1464
  File.open(path, "rb") { |io| full.public_send("\#{attr}=", io) }
1384
1465
  end
1466
+
1467
+ # multi_image_field demo: an *array* assignment through the plural
1468
+ # mount_uploaders. One file is enough to exercise the array path.
1469
+ gallery_src = seed_uploads.join("sample_cover.png")
1470
+ if full.respond_to?(:gallery) && gallery_src.file? && full.gallery.blank?
1471
+ File.open(gallery_src, "rb") { |io| full.gallery = [io] }
1472
+ end
1385
1473
  full.save! if full.changed?
1386
1474
 
1387
1475
  # "Empty" refers to the role and uploader fields (their empty
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+
2
3
  require "active_support/core_ext/string/inflections"
3
4
 
4
5
  module InlineFormsInstaller
@@ -47,7 +48,7 @@ module InlineFormsInstaller
47
48
  def join_table
48
49
  return "roles_users" if default?
49
50
 
50
- [table_name, "roles"].sort.join("_")
51
+ [ table_name, "roles" ].sort.join("_")
51
52
  end
52
53
 
53
54
  def controller_name
@@ -1,6 +1,7 @@
1
1
  # -*- encoding : utf-8 -*-
2
+
2
3
  module InlineFormsInstaller
3
- VERSION = "8.1.20"
4
+ VERSION = "8.1.40"
4
5
 
5
6
  # Canonical bare Ruby version (must match gemspec `required_ruby_version`).
6
7
  # Written verbatim into generated apps' `.ruby-version` for rbenv/chruby/asdf/
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+
2
3
  require "inline_forms_installer/version"
3
4
 
4
5
  module InlineFormsInstaller
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "../example_app/example_integration_test_case"
4
4
 
5
- # Example app includes opening_date:date on Apartment (jQuery UI datepicker).
5
+ # Example app includes opening_date:date on Apartment (native date input since 8.1.25).
6
6
  class ExampleAppApartmentOpeningDateTest < ExampleAppIntegrationTestCase
7
7
  setup do
8
8
  @apartment = Apartment.find_or_create_by!(name: "Datepicker Apt") do |a|
@@ -24,7 +24,7 @@ class ExampleAppApartmentOpeningDateTest < ExampleAppIntegrationTestCase
24
24
  assert_includes @response.body, "15-03-2019"
25
25
  end
26
26
 
27
- test "inline edit opening_date uses datepicker class hook" do
27
+ test "inline edit opening_date renders a native date input" do
28
28
  get edit_apartment_path(
29
29
  @apartment,
30
30
  attribute: "opening_date",
@@ -33,17 +33,18 @@ class ExampleAppApartmentOpeningDateTest < ExampleAppIntegrationTestCase
33
33
  ), headers: @turbo_headers
34
34
 
35
35
  assert_response :success
36
- assert_includes @response.body, %(class="datepicker")
36
+ assert_includes @response.body, %(type="date")
37
37
  assert_includes @response.body, %(name="opening_date")
38
+ assert_includes @response.body, %(value="2019-03-15"), "value must be ISO 8601"
38
39
  refute_includes @response.body, "<script",
39
- "datepicker init is centralized in inline_forms.js"
40
+ "native inputs need no per-field init script"
40
41
  end
41
42
 
42
- test "new apartment form includes datepicker for opening_date" do
43
+ test "new apartment form includes a native date input for opening_date" do
43
44
  get new_apartment_path(update: @list_frame), headers: @list_headers
44
45
 
45
46
  assert_response :success
46
47
  assert_includes @response.body, %(name="opening_date")
47
- assert_includes @response.body, %(class="datepicker")
48
+ assert_includes @response.body, %(type="date")
48
49
  end
49
50
  end
@@ -67,4 +67,32 @@ class ExampleAppApartmentTopLevelNewTest < ExampleAppIntegrationTestCase
67
67
  assert_includes @response.body, %(<turbo-frame id="#{@frame}">)
68
68
  assert_includes @response.body, name
69
69
  end
70
+
71
+ # "Open after create": Apartment has photos:associated, so a turbo-stream
72
+ # create response refreshes the list AND opens the new row, landing the user
73
+ # where the photos panel (only available on a persisted record) is usable.
74
+ test "top-level create with turbo-stream accept opens the new row" do
75
+ name = "AAA-OpenAfterCreate-#{SecureRandom.hex(4)}"
76
+ assert_difference("Apartment.count", 1) do
77
+ post apartments_path(update: @frame),
78
+ params: { name: name, title: "Open after create test" },
79
+ headers: { "Turbo-Frame" => @frame,
80
+ "Accept" => "text/vnd.turbo-stream.html, text/html" }
81
+ end
82
+ assert_response :success
83
+ assert_equal "text/vnd.turbo-stream.html", @response.media_type
84
+
85
+ apartment = Apartment.find_by!(name: name)
86
+ assert_includes @response.body,
87
+ %(<turbo-stream action="replace" target="#{@frame}">)
88
+ assert_includes @response.body,
89
+ %(<turbo-stream action="replace" target="apartment_#{apartment.id}">)
90
+ assert_includes @response.body,
91
+ %(id="apartment_#{apartment.id}_photos_list_auto_header")
92
+ assert_match(
93
+ %r{/photos/new\?[^"]*parent_id=#{apartment.id}},
94
+ @response.body,
95
+ "expected the photos panel's new link for the created apartment"
96
+ )
97
+ end
70
98
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # Nesting-in-nesting (Apartment -> Photo -> Comment): a Photo opened inside an
6
+ # apartment's photos panel exposes its own :associated comments panel, and the
7
+ # whole nested CRUD contract works one level deeper.
8
+ class ExampleAppPhotoCommentsTest < ExampleAppIntegrationTestCase
9
+ setup do
10
+ @apartment = Apartment.create!(name: "Nested", title: "Nesting demo")
11
+ @photo = @apartment.photos.create!(name: "room.jpg", caption: "the room")
12
+ end
13
+
14
+ test "comments are not served as standalone html resource" do
15
+ assert Comment.not_accessible_through_html?
16
+ get comments_path
17
+ assert_not response.successful?,
18
+ "expected no standalone HTML index for not_accessible_through_html model (got #{response.status})"
19
+ end
20
+
21
+ test "opened nested photo row exposes the comments panel with its new link" do
22
+ row_frame = "apartment_#{@apartment.id}_photo_#{@photo.id}"
23
+ get photo_path(@photo, update: row_frame),
24
+ headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
25
+ assert_response :success
26
+
27
+ panel = "photo_#{@photo.id}_comments"
28
+ assert_includes @response.body, %(id="#{panel}_list_auto_header")
29
+ assert_includes @response.body, %(<turbo-frame id="#{panel}")
30
+ assert_match(
31
+ %r{/comments/new\?[^"]*parent_id=#{@photo.id}},
32
+ @response.body,
33
+ "expected the comments panel's new-record link for the photo"
34
+ )
35
+ end
36
+
37
+ test "comments panel lists existing comments" do
38
+ comment = @photo.comments.create!(body: "First!")
39
+ row_frame = "apartment_#{@apartment.id}_photo_#{@photo.id}"
40
+ get photo_path(@photo, update: row_frame),
41
+ headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
42
+ assert_response :success
43
+ assert_includes @response.body,
44
+ %(<turbo-frame id="photo_#{@photo.id}_comment_#{comment.id}">)
45
+ assert_includes @response.body, "First!"
46
+ end
47
+
48
+ test "new comment form renders inside the comments panel frame" do
49
+ panel = "photo_#{@photo.id}_comments"
50
+ get new_comment_path(update: panel, parent_class: "Photo", parent_id: @photo.id),
51
+ headers: { "Turbo-Frame" => panel, "Accept" => "text/html" }
52
+ assert_response :success
53
+ assert_includes @response.body, %(<turbo-frame id="#{panel}">)
54
+ assert_includes @response.body, 'name="body"'
55
+ end
56
+
57
+ test "nested comment create persists with the photo FK and returns the panel frame" do
58
+ panel = "photo_#{@photo.id}_comments"
59
+ assert_difference("Comment.count", 1) do
60
+ post comments_path(update: panel, parent_class: "Photo", parent_id: @photo.id),
61
+ params: { body: "Great view from here" },
62
+ headers: { "Turbo-Frame" => panel,
63
+ "Accept" => "text/vnd.turbo-stream.html, text/html" }
64
+ end
65
+ assert_response :success
66
+ # Comment has no :associated panel of its own, so open-after-create does
67
+ # not fire; the nested flow keeps the plain frame response.
68
+ assert_equal "text/html", @response.media_type
69
+ assert_includes @response.body, %(<turbo-frame id="#{panel}">)
70
+
71
+ comment = Comment.find_by!(body: "Great view from here")
72
+ assert_equal @photo.id, comment.photo_id
73
+ end
74
+
75
+ test "nested comment row opens and closes" do
76
+ comment = @photo.comments.create!(body: "Open me")
77
+ row_frame = "photo_#{@photo.id}_comment_#{comment.id}"
78
+
79
+ get comment_path(comment, update: row_frame),
80
+ headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
81
+ assert_response :success
82
+ assert_includes @response.body, %(<turbo-frame id="#{row_frame}">)
83
+ assert_includes @response.body, "Open me"
84
+
85
+ get comment_path(comment, update: row_frame, close: true),
86
+ headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
87
+ assert_response :success
88
+ assert_includes @response.body, %(<turbo-frame id="#{row_frame}">)
89
+ end
90
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # The installer enables assume_ssl/force_ssl in the generated production
6
+ # config (8.1.36): generated apps deploy behind an SSL-terminating proxy and
7
+ # the Devise mailer config assumes https. Guards Brakeman's ForceSSL check
8
+ # (the full-gate CI scans this app) against a silent regression, e.g. the
9
+ # Rails template shifting its comment format so the installer's gsub misses.
10
+ class ExampleAppProductionSslTest < ExampleAppIntegrationTestCase
11
+ test "production config forces SSL" do
12
+ config = File.read(Rails.root.join("config", "environments", "production.rb"))
13
+
14
+ assert_match(/^\s*config\.assume_ssl = true/, config,
15
+ "expected the installer to enable config.assume_ssl in production.rb")
16
+ assert_match(/^\s*config\.force_ssl = true/, config,
17
+ "expected the installer to enable config.force_ssl in production.rb")
18
+ end
19
+ end
@@ -12,7 +12,8 @@ class ExampleAppShowcaseDateTimeFieldsTest < ExampleAppIntegrationTestCase
12
12
  @showcase = FormElementShowcase.find_or_create_by!(title: "Date/time demo")
13
13
  end
14
14
 
15
- test "date_select meeting_date renders datepicker on edit" do
15
+ test "date_select meeting_date renders native date input on edit" do
16
+ @showcase.update!(meeting_date: Date.new(2026, 5, 17))
16
17
  frame = "form_element_showcase_#{@showcase.id}_meeting_date"
17
18
  headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
18
19
 
@@ -25,11 +26,13 @@ class ExampleAppShowcaseDateTimeFieldsTest < ExampleAppIntegrationTestCase
25
26
 
26
27
  assert_response :success
27
28
  assert_includes @response.body, %(<turbo-frame id="#{frame}">)
28
- assert_includes @response.body, %(class="datepicker")
29
+ assert_includes @response.body, %(type="date")
29
30
  assert_includes @response.body, %(name="meeting_date")
31
+ assert_includes @response.body, %(value="2026-05-17"), "value must be ISO 8601"
32
+ refute_includes @response.body, "<script", "no inline picker init"
30
33
  end
31
34
 
32
- test "time_select meeting_time renders timepicker on edit" do
35
+ test "time_select meeting_time renders native time input on edit" do
33
36
  @showcase.update!(meeting_time: Time.utc(2000, 1, 1, 9, 15))
34
37
  frame = "form_element_showcase_#{@showcase.id}_meeting_time"
35
38
  headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
@@ -43,8 +46,42 @@ class ExampleAppShowcaseDateTimeFieldsTest < ExampleAppIntegrationTestCase
43
46
 
44
47
  assert_response :success
45
48
  assert_includes @response.body, %(<turbo-frame id="#{frame}">)
46
- assert_includes @response.body, %(class="timepicker")
49
+ assert_includes @response.body, %(type="time")
47
50
  assert_includes @response.body, %(name="meeting_time")
51
+ assert_includes @response.body, %(value="09:15"), "value must be 24h HH:MM"
52
+ end
53
+
54
+ test "time_select round-trips a native HH:MM submission" do
55
+ @showcase.update!(meeting_time: Time.utc(2000, 1, 1, 9, 15))
56
+ frame = "form_element_showcase_#{@showcase.id}_meeting_time"
57
+
58
+ put form_element_showcase_path(
59
+ @showcase,
60
+ attribute: "meeting_time",
61
+ form_element: "time_select",
62
+ update: frame
63
+ ), params: { meeting_time: "14:30" },
64
+ headers: { "Turbo-Frame" => frame, "Accept" => "text/html" }
65
+
66
+ assert_response :success
67
+ @showcase.reload
68
+ assert_equal 14, @showcase.meeting_time.hour
69
+ assert_equal 30, @showcase.meeting_time.min
70
+ end
71
+
72
+ test "date_select round-trips a native ISO submission" do
73
+ frame = "form_element_showcase_#{@showcase.id}_meeting_date"
74
+
75
+ put form_element_showcase_path(
76
+ @showcase,
77
+ attribute: "meeting_date",
78
+ form_element: "date_select",
79
+ update: frame
80
+ ), params: { meeting_date: "2027-01-09" },
81
+ headers: { "Turbo-Frame" => frame, "Accept" => "text/html" }
82
+
83
+ assert_response :success
84
+ assert_equal Date.new(2027, 1, 9), @showcase.reload.meeting_date
48
85
  end
49
86
 
50
87
  test "month_select birth_month renders 12 options on edit" do
@@ -64,7 +101,7 @@ class ExampleAppShowcaseDateTimeFieldsTest < ExampleAppIntegrationTestCase
64
101
  assert_match(/<select[^>]+name="date\[birth_month\]"/, @response.body)
65
102
  end
66
103
 
67
- test "month_year_picker start_month renders datepicker class hook" do
104
+ test "month_year_picker start_month renders native month input on edit" do
68
105
  @showcase.update!(start_month: Date.new(2026, 5, 1))
69
106
  frame = "form_element_showcase_#{@showcase.id}_start_month"
70
107
  headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
@@ -78,7 +115,33 @@ class ExampleAppShowcaseDateTimeFieldsTest < ExampleAppIntegrationTestCase
78
115
 
79
116
  assert_response :success
80
117
  assert_includes @response.body, %(<turbo-frame id="#{frame}">)
81
- assert_includes @response.body, %(datepicker-month-year)
118
+ assert_includes @response.body, %(type="month")
82
119
  assert_includes @response.body, %(name="start_month")
120
+ assert_includes @response.body, %(value="2026-05"), "value must be ISO YYYY-MM"
121
+ end
122
+
123
+ test "month_year_picker round-trips native YYYY-MM and legacy 'Month YYYY' submissions" do
124
+ frame = "form_element_showcase_#{@showcase.id}_start_month"
125
+ headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
126
+
127
+ # Native <input type="month"> value.
128
+ put form_element_showcase_path(
129
+ @showcase,
130
+ attribute: "start_month",
131
+ form_element: "month_year_picker",
132
+ update: frame
133
+ ), params: { start_month: "2026-09" }, headers: headers
134
+ assert_response :success
135
+ assert_equal Date.new(2026, 9, 1), @showcase.reload.start_month
136
+
137
+ # Legacy pre-8.1.25 display format still parses via the fallback.
138
+ put form_element_showcase_path(
139
+ @showcase,
140
+ attribute: "start_month",
141
+ form_element: "month_year_picker",
142
+ update: frame
143
+ ), params: { start_month: "November 2027" }, headers: headers
144
+ assert_response :success
145
+ assert_equal Date.new(2027, 11, 1), @showcase.reload.start_month
83
146
  end
84
147
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # multi_image_field (FormElementShowcase#gallery, 8.1.28): a mount_uploaders
6
+ # (plural) CarrierWave column serialized as a JSON array. First example-app
7
+ # coverage for this Tier 1 element.
8
+ class ExampleAppShowcaseGalleryTest < ExampleAppIntegrationTestCase
9
+ setup do
10
+ @showcase = FormElementShowcase.find_or_create_by!(title: "Gallery demo")
11
+ src = Rails.root.join("db", "seed_uploads", "sample_cover.png")
12
+ if @showcase.gallery.blank? && src.file?
13
+ File.open(src, "rb") { |io| @showcase.gallery = [io] }
14
+ @showcase.save!
15
+ end
16
+ end
17
+
18
+ test "show renders the gallery images from the plural uploader" do
19
+ skip "seed image missing" if @showcase.gallery.blank?
20
+
21
+ frame = "form_element_showcase_#{@showcase.id}"
22
+ get form_element_showcase_path(@showcase, update: frame),
23
+ headers: { "Turbo-Frame" => frame, "Accept" => "text/html" }
24
+
25
+ assert_response :success
26
+ assert_includes @response.body, %(<turbo-frame id="#{frame}_gallery">)
27
+ # mount_uploaders store_dir => uploads/form_element_showcase/gallery/:id/
28
+ assert_includes @response.body, "uploads/form_element_showcase/gallery",
29
+ "expected an <img> from the gallery uploader array"
30
+ end
31
+
32
+ test "edit renders a multiple file input submitting gallery[]" do
33
+ frame = "form_element_showcase_#{@showcase.id}_gallery"
34
+ get edit_form_element_showcase_path(
35
+ @showcase,
36
+ attribute: "gallery",
37
+ form_element: "multi_image_field",
38
+ update: frame
39
+ ), headers: { "Turbo-Frame" => frame, "Accept" => "text/html" }
40
+
41
+ assert_response :success
42
+ assert_includes @response.body, %(type="file")
43
+ assert_includes @response.body, %(name="gallery[]")
44
+ assert_includes @response.body, %(multiple="multiple")
45
+ end
46
+
47
+ test "empty gallery renders the inline-edit plus link" do
48
+ empty = FormElementShowcase.find_or_create_by!(title: "Gallery-less demo") do |s|
49
+ s.is_active = false
50
+ s.gender = 1
51
+ s.rating_int = 1
52
+ s.priority = 1
53
+ s.priority2 = 1
54
+ s.stars = 1
55
+ s.scale_int = 1
56
+ s.scale_val = 1
57
+ end
58
+ frame = "form_element_showcase_#{empty.id}"
59
+
60
+ get form_element_showcase_path(empty, update: frame),
61
+ headers: { "Turbo-Frame" => frame, "Accept" => "text/html" }
62
+
63
+ assert_response :success
64
+ gallery_frame = @response.body[/<turbo-frame id="#{frame}_gallery">.*?<\/turbo-frame>/m]
65
+ assert gallery_frame, "expected the gallery attribute frame"
66
+ assert_includes gallery_frame, "fi-plus"
67
+ end
68
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # Per-user locale (inline_forms 8.1.31): ApplicationController#switch_locale
6
+ # wraps every request in I18n.with_locale(current_user.locale.name), falling
7
+ # back to the default for unknown/blank names. Observable via the layout's
8
+ # <html lang="..."> attribute.
9
+ class ExampleAppUserLocaleTest < ExampleAppIntegrationTestCase
10
+ test "html lang reflects the default locale" do
11
+ get apartments_path
12
+
13
+ assert_response :success
14
+ assert_includes @response.body, %(<html lang="en">)
15
+ end
16
+
17
+ test "user locale preference switches I18n for the request" do
18
+ admin = User.find_by!(email: "admin@example.com")
19
+ nl = Locale.find_or_create_by!(name: "nl") { |l| l.title = "Nederlands" }
20
+ admin.update!(locale: nl)
21
+ sign_in admin
22
+
23
+ get apartments_path
24
+
25
+ assert_response :success
26
+ assert_includes @response.body, %(<html lang="nl">)
27
+ ensure
28
+ en = Locale.find_by(name: "en")
29
+ admin&.update!(locale: en) if en
30
+ end
31
+
32
+ test "unknown locale name falls back to the default" do
33
+ admin = User.find_by!(email: "admin@example.com")
34
+ xx = Locale.find_or_create_by!(name: "xx") { |l| l.title = "Not a locale" }
35
+ admin.update!(locale: xx)
36
+ sign_in admin
37
+
38
+ get apartments_path
39
+
40
+ assert_response :success
41
+ assert_includes @response.body, %(<html lang="en">)
42
+ ensure
43
+ en = Locale.find_by(name: "en")
44
+ admin&.update!(locale: en) if en
45
+ end
46
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../example_app/example_integration_test_case"
4
+
5
+ # Pattern 1 preset theming (inline_forms 8.1.29): the user's integer `theme`
6
+ # column maps to a `theme-<name>` body class; palettes live in the engine's
7
+ # _theme.scss as --if-color-* custom-property overrides.
8
+ class ExampleAppUserThemeTest < ExampleAppIntegrationTestCase
9
+ test "body carries the default theme class" do
10
+ get apartments_path
11
+
12
+ assert_response :success
13
+ assert_includes @response.body, %(<body class="theme-default">)
14
+ end
15
+
16
+ test "user theme preference switches the body class" do
17
+ admin = User.find_by!(email: "admin@example.com")
18
+ admin.update!(theme: 1)
19
+ # Warden's test login caches the user *object* from setup's sign_in;
20
+ # re-sign-in so current_user carries the updated theme.
21
+ sign_in admin
22
+
23
+ get apartments_path
24
+
25
+ assert_response :success
26
+ assert_includes @response.body, %(<body class="theme-dark">)
27
+ ensure
28
+ admin&.update!(theme: 0)
29
+ end
30
+
31
+ test "high-contrast maps the css-safe class name" do
32
+ admin = User.find_by!(email: "admin@example.com")
33
+ admin.update!(theme: 3)
34
+ sign_in admin
35
+
36
+ get apartments_path
37
+
38
+ assert_response :success
39
+ assert_includes @response.body, %(<body class="theme-high-contrast">)
40
+ ensure
41
+ admin&.update!(theme: 0)
42
+ end
43
+
44
+ test "theme dropdown renders on the user show panel" do
45
+ admin = User.find_by!(email: "admin@example.com")
46
+ frame = "user_#{admin.id}"
47
+
48
+ get user_path(admin, update: frame),
49
+ headers: { "Turbo-Frame" => frame, "Accept" => "text/html" }
50
+
51
+ assert_response :success
52
+ assert_includes @response.body, %(<turbo-frame id="user_#{admin.id}_theme">)
53
+ assert_includes @response.body, %(<turbo-frame id="user_#{admin.id}_primary_color">)
54
+ end
55
+
56
+ test "user color overrides render as a scoped inline style (Pattern 2)" do
57
+ admin = User.find_by!(email: "admin@example.com")
58
+ admin.update!(primary_color: "#2563eb")
59
+ sign_in admin
60
+
61
+ get apartments_path
62
+
63
+ assert_response :success
64
+ assert_includes @response.body, %(id="inline_forms_user_color_overrides")
65
+ assert_includes @response.body, "body.theme-default { --if-color-primary: #2563eb; }"
66
+ ensure
67
+ admin&.update!(primary_color: nil)
68
+ end
69
+
70
+ test "no override style tag renders when the user has no custom colors" do
71
+ get apartments_path
72
+
73
+ assert_response :success
74
+ refute_includes @response.body, %(id="inline_forms_user_color_overrides")
75
+ end
76
+ end
@@ -2,9 +2,9 @@
2
2
  Owner sub-tabs (Turbo). Each tab link targets the surrounding row
3
3
  turbo-frame (id == @update_span) so clicking a tab re-fetches
4
4
  OwnersController#show?tab=... and swaps just that frame. Active-tab
5
- highlighting is driven by set_tab / current_tab? (tabs_on_rails).
6
- InlineForms::TurboTabsBuilder threads link_options: through to the
7
- anchor; upstream tabs_on_rails 3.0 can only annotate the surrounding li.
5
+ highlighting is driven by set_tab / current_tab? (InlineForms::Tabs,
6
+ vendored since 8.1.23). InlineForms::TurboTabsBuilder threads
7
+ link_options: through to the anchor.
8
8
  -%>
9
9
  <div class="row owner_tabs_row">
10
10
  <div class='medium-1 large-1 column'>&nbsp;</div>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inline_forms_installer
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.20
4
+ version: 8.1.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ace Suares
@@ -77,6 +77,15 @@ files:
77
77
  - lib/installer_templates/example_app_assets/sample.txt
78
78
  - lib/installer_templates/example_app_assets/sample.wav
79
79
  - lib/installer_templates/example_app_assets/sample_cover.png
80
+ - lib/installer_templates/example_app_assets/seed_images/apt1_bedroom.png
81
+ - lib/installer_templates/example_app_assets/seed_images/apt1_kitchen.png
82
+ - lib/installer_templates/example_app_assets/seed_images/apt1_living.png
83
+ - lib/installer_templates/example_app_assets/seed_images/apt2_kitchen.png
84
+ - lib/installer_templates/example_app_assets/seed_images/apt2_living.png
85
+ - lib/installer_templates/example_app_assets/seed_images/apt2_terrace.png
86
+ - lib/installer_templates/example_app_assets/seed_images/apt3_living.png
87
+ - lib/installer_templates/example_app_assets/seed_images/apt3_loft.png
88
+ - lib/installer_templates/example_app_assets/seed_images/apt3_view.png
80
89
  - lib/installer_templates/example_app_tests/test/example_app/example_integration_test_case.rb
81
90
  - lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb
82
91
  - lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb
@@ -89,11 +98,14 @@ files:
89
98
  - lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb
90
99
  - lib/installer_templates/example_app_tests/test/integration/example_app_guest_access_test.rb
91
100
  - lib/installer_templates/example_app_tests/test/integration/example_app_owner_tabs_test.rb
101
+ - lib/installer_templates/example_app_tests/test/integration/example_app_photo_comments_test.rb
92
102
  - lib/installer_templates/example_app_tests/test/integration/example_app_photo_revert_test.rb
93
103
  - lib/installer_templates/example_app_tests/test/integration/example_app_photos_test.rb
104
+ - lib/installer_templates/example_app_tests/test/integration/example_app_production_ssl_test.rb
94
105
  - lib/installer_templates/example_app_tests/test/integration/example_app_routing_test.rb
95
106
  - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_choice_scale_fields_test.rb
96
107
  - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_date_time_fields_test.rb
108
+ - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_gallery_test.rb
97
109
  - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_locales_associations_test.rb
98
110
  - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_money_update_test.rb
99
111
  - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_numeric_fields_test.rb
@@ -102,6 +114,8 @@ files:
102
114
  - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_text_fields_test.rb
103
115
  - lib/installer_templates/example_app_tests/test/integration/example_app_showcase_validation_hints_test.rb
104
116
  - lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb
117
+ - lib/installer_templates/example_app_tests/test/integration/example_app_user_locale_test.rb
118
+ - lib/installer_templates/example_app_tests/test/integration/example_app_user_theme_test.rb
105
119
  - lib/installer_templates/example_app_tests/test/integration/example_app_validation_hints_test.rb
106
120
  - lib/installer_templates/example_app_tests/test/models/example_app_apartment_name_validation_test.rb
107
121
  - lib/installer_templates/example_app_tests/test/models/example_app_apartment_photo_test.rb