inline_forms_installer 8.1.21 → 8.1.42
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.
- checksums.yaml +4 -4
- data/inline_forms_installer.gemspec +9 -9
- data/lib/inline_forms_installer/creator.rb +11 -7
- data/lib/inline_forms_installer/installer_core.rb +187 -44
- data/lib/inline_forms_installer/user_model_config.rb +2 -1
- data/lib/inline_forms_installer/version.rb +2 -1
- data/lib/inline_forms_installer.rb +1 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt1_bedroom.png +0 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt1_kitchen.png +0 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt1_living.png +0 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt2_kitchen.png +0 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt2_living.png +0 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt2_terrace.png +0 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt3_living.png +0 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt3_loft.png +0 -0
- data/lib/installer_templates/example_app_assets/seed_images/apt3_view.png +0 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_addto_field_test.rb +54 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_opening_date_test.rb +7 -6
- data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +28 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_photo_comments_test.rb +90 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_production_ssl_test.rb +19 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_schema_edit_test.rb +75 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_showcase_date_time_fields_test.rb +69 -6
- data/lib/installer_templates/example_app_tests/test/integration/example_app_showcase_gallery_test.rb +68 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_user_locale_test.rb +46 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_user_theme_test.rb +76 -0
- data/lib/installer_templates/example_app_views/owners/_owner_tabs.html.erb +3 -3
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d09ceda3c59a9d785f9a7270e58254f1ace6e43ed605f9681e8046c1ad67f8a7
|
|
4
|
+
data.tar.gz: 35178d8b5bf044d7f941efa611bd39512f3060bc996b7669a12ed9e8c6721e6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c57d71b4fa224a31135385f43175c50fcf4e54611965797726b3a8ea4dd89233e9bcac54a2f4180fbf8a5c692f9cc0b7710adee7040bca89dd291c59c43fe178
|
|
7
|
+
data.tar.gz: 7ca6d416410ef9d1342c6bd6339b3be6f6bdda2923465d1653d357729c0b3b94a90b2f00ed98e33f855fda4722d0b5a0580436c5358fdae278119d9bf9661ce1
|
|
@@ -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
|
|
13
|
-
s.summary = %q
|
|
14
|
-
s.description = %q
|
|
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,14 @@ 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
|
|
20
|
-
method_option :database, :
|
|
21
|
-
method_option :example, :
|
|
22
|
-
method_option :
|
|
23
|
-
method_option :
|
|
24
|
-
method_option :
|
|
25
|
-
method_option :
|
|
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 :schema_edit, type: :boolean, default: false, desc: "mount the schema-change GUI (inline_forms_schema_edit gem); implied by --example"
|
|
24
|
+
method_option :email, aliases: "-e", default: "admin@example.com", desc: "specify admin email"
|
|
25
|
+
method_option :password, aliases: "-p", default: "admin999", desc: "specify admin password"
|
|
26
|
+
method_option :skiprvm, aliases: "--no-rvm", type: :boolean, default: false, desc: "install inline_forms without RVM"
|
|
27
|
+
method_option :user_model, aliases: "-U", default: "User", banner: "CLASS", desc: "Devise model class (e.g. Member); Warden scope stays :user (current_user)"
|
|
26
28
|
|
|
27
29
|
def create(app_name)
|
|
28
30
|
def self.skiprvm
|
|
@@ -124,6 +126,8 @@ module InlineFormsInstaller
|
|
|
124
126
|
ENV["using_sqlite"] = using_sqlite?.to_s
|
|
125
127
|
ENV["database"] = database
|
|
126
128
|
ENV["install_example"] = install_example?.to_s
|
|
129
|
+
# The example app always mounts the schema GUI (it demos + tests it).
|
|
130
|
+
ENV["install_schema_edit"] = (options[:schema_edit] || install_example?).to_s
|
|
127
131
|
# Match the generated `.ruby-version` to the version manager actually in
|
|
128
132
|
# use. RVM's `.ruby-version` reader needs the `ruby-X.Y.Z` form — a bare
|
|
129
133
|
# `X.Y.Z` makes `rvm use .` bail with "do not know how to handle" and skip
|
|
@@ -45,7 +45,7 @@ def install_prerelease_gems_from_roots!
|
|
|
45
45
|
roots.uniq!
|
|
46
46
|
return if roots.empty?
|
|
47
47
|
|
|
48
|
-
%w[validation_hints inline_forms inline_forms_installer].each do |name|
|
|
48
|
+
%w[validation_hints inline_forms inline_forms_installer inline_forms_schema_edit].each do |name|
|
|
49
49
|
# Pick the *highest version*, not the highest filename. String sort
|
|
50
50
|
# placed `inline_forms-8.1.7.gem` above `inline_forms-8.1.10.gem`
|
|
51
51
|
# because "7" > "1" lexicographically — silently picking up a stale
|
|
@@ -62,7 +62,13 @@ def install_prerelease_gems_from_roots!
|
|
|
62
62
|
# the exact shape that bit us between 8.1.6 (default gemset's
|
|
63
63
|
# highest installer) and 8.1.10.
|
|
64
64
|
candidates = roots.flat_map { |root|
|
|
65
|
-
|
|
65
|
+
# inline_forms_schema_edit lives in a subdirectory of the inline_forms
|
|
66
|
+
# checkout with its own pkg/; glob both layouts for every name (the
|
|
67
|
+
# extra patterns simply match nothing for the root-level gems).
|
|
68
|
+
Dir[File.join(root, "#{name}-*.gem"),
|
|
69
|
+
File.join(root, "pkg", "#{name}-*.gem"),
|
|
70
|
+
File.join(root, name, "#{name}-*.gem"),
|
|
71
|
+
File.join(root, name, "pkg", "#{name}-*.gem")]
|
|
66
72
|
}
|
|
67
73
|
gem_file = candidates.max_by do |path|
|
|
68
74
|
ver_str = File.basename(path, ".gem").sub(/\A#{Regexp.escape(name)}-/, "")
|
|
@@ -103,7 +109,7 @@ end
|
|
|
103
109
|
|
|
104
110
|
add_source 'https://rubygems.org'
|
|
105
111
|
|
|
106
|
-
gem 'cancancan'
|
|
112
|
+
gem 'cancancan', '~> 3.6'
|
|
107
113
|
gem 'carrierwave', '~> 3.1'
|
|
108
114
|
gem 'devise', '~> 5.0'
|
|
109
115
|
gem 'devise-i18n', '~> 1.16'
|
|
@@ -119,12 +125,23 @@ if ENV["INLINE_FORMS_GEMFILE_PATH"] && File.directory?(ENV["INLINE_FORMS_GEMFILE
|
|
|
119
125
|
else
|
|
120
126
|
gem "inline_forms", "~> 8"
|
|
121
127
|
end
|
|
128
|
+
# Schema-change GUI (separate mountable engine gem): only when requested via
|
|
129
|
+
# `--schema-edit` (implied by `--example`). Apps that never change their own
|
|
130
|
+
# schema ship without this surface entirely.
|
|
131
|
+
if ENV["install_schema_edit"] == "true"
|
|
132
|
+
schema_edit_path = ENV["INLINE_FORMS_GEMFILE_PATH"] &&
|
|
133
|
+
File.join(ENV["INLINE_FORMS_GEMFILE_PATH"], "inline_forms_schema_edit")
|
|
134
|
+
if schema_edit_path && File.directory?(schema_edit_path)
|
|
135
|
+
gem "inline_forms_schema_edit", path: schema_edit_path
|
|
136
|
+
else
|
|
137
|
+
gem "inline_forms_schema_edit", "~> 8"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
# jQuery is required by Foundation 6's JS only. jQuery UI is fully gone since
|
|
141
|
+
# 8.1.26 (native date/time/month inputs in 8.1.25; datalist combobox + range
|
|
142
|
+
# slider in 8.1.26), as are jquery-timepicker-rails, jquery-ui-rails and
|
|
143
|
+
# rails-jquery-autocomplete.
|
|
122
144
|
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
145
|
# Foundation Icons SCSS + fonts are vendored in the inline_forms engine (Dart Sass;
|
|
129
146
|
# foundation-icons-sass-rails depended on sass-rails).
|
|
130
147
|
gem 'mini_magick'
|
|
@@ -138,7 +155,6 @@ gem 'money-rails', '~> 3.0'
|
|
|
138
155
|
gem 'mysql2'
|
|
139
156
|
gem 'paper_trail', '~> 17.0'
|
|
140
157
|
gem 'rails-i18n', '~> 8.1'
|
|
141
|
-
gem 'rails-jquery-autocomplete'
|
|
142
158
|
gem 'rails', '~> 8.1'
|
|
143
159
|
gem 'rake'
|
|
144
160
|
# NOTE: the `rvm` gem is intentionally NOT a runtime dependency of generated
|
|
@@ -156,10 +172,11 @@ gem 'importmap-rails'
|
|
|
156
172
|
# use `<turbo-frame>` + HTML responses (see docs/ujs-to-turbo.md). Registers the
|
|
157
173
|
# `turbo_stream` MIME type for optional stream responses.
|
|
158
174
|
gem 'turbo-rails'
|
|
159
|
-
|
|
175
|
+
# tabs (set_tab / tabs_tag) are vendored in the inline_forms engine since
|
|
176
|
+
# 8.1.23 (InlineForms::Tabs) — the unmaintained tabs_on_rails gem is gone.
|
|
160
177
|
gem 'unicorn'
|
|
161
178
|
gem 'validation_hints', '~> 8'
|
|
162
|
-
gem 'will_paginate'
|
|
179
|
+
gem 'will_paginate', '~> 4.0'
|
|
163
180
|
|
|
164
181
|
gem_group :test do
|
|
165
182
|
# Rails 7 still expects Minitest 5; 6.x breaks the railties test runner.
|
|
@@ -353,6 +370,13 @@ class #{user_cfg.devise_migration_class} < ActiveRecord::Migration[8.1]
|
|
|
353
370
|
|
|
354
371
|
t.string :name
|
|
355
372
|
t.integer :locale_id
|
|
373
|
+
# Preset UI theme (inline_forms 8.1.29+): 0 default, 1 dark, 2 sepia,
|
|
374
|
+
# 3 high-contrast — see #{user_cfg.class_name}::INLINE_FORMS_THEMES.
|
|
375
|
+
t.integer :theme, default: 0, null: false
|
|
376
|
+
# Per-user color overrides (inline_forms 8.1.32+, theming Pattern 2):
|
|
377
|
+
# hex "#rrggbb" or NULL; rendered as --if-color-* overrides in <head>.
|
|
378
|
+
t.string :primary_color
|
|
379
|
+
t.string :accent_color
|
|
356
380
|
|
|
357
381
|
t.timestamps
|
|
358
382
|
end
|
|
@@ -396,6 +420,10 @@ create_file user_cfg.model_path, <<-USER_MODEL.strip_heredoc
|
|
|
396
420
|
|
|
397
421
|
# validations
|
|
398
422
|
validates :name, :presence => true
|
|
423
|
+
# Theming Pattern 2 color overrides; the color_field element only ever
|
|
424
|
+
# submits "#rrggbb" or nil, but validate anyway (console edits, imports).
|
|
425
|
+
validates :primary_color, format: { with: /\\A#\\h{6}\\z/i }, allow_blank: true
|
|
426
|
+
validates :accent_color, format: { with: /\\A#\\h{6}\\z/i }, allow_blank: true
|
|
399
427
|
|
|
400
428
|
# Default ordering for inline_forms list views (and any explicit caller
|
|
401
429
|
# via `#{user_cfg.class_name}.inline_forms_list`). Avoids `default_scope`
|
|
@@ -415,12 +443,32 @@ create_file user_cfg.model_path, <<-USER_MODEL.strip_heredoc
|
|
|
415
443
|
return !!self.roles.find_by_name(role)
|
|
416
444
|
end
|
|
417
445
|
|
|
446
|
+
# Preset UI themes (inline_forms Pattern 1 theming). The integer `theme`
|
|
447
|
+
# column maps to a body class `theme-<name>`; the palettes ship in the
|
|
448
|
+
# inline_forms engine (_theme.scss). The engine layout calls
|
|
449
|
+
# #inline_forms_theme on current_user when the method exists.
|
|
450
|
+
INLINE_FORMS_THEMES = { 0 => 'default', 1 => 'dark', 2 => 'sepia', 3 => 'high-contrast' }.freeze
|
|
451
|
+
|
|
452
|
+
def inline_forms_theme
|
|
453
|
+
INLINE_FORMS_THEMES.fetch(theme.to_i, 'default')
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# Theming Pattern 2: --if-color-* overrides for this user, rendered as an
|
|
457
|
+
# inline <style> by the engine layout (inline_forms_user_color_overrides_style).
|
|
458
|
+
# Keys are --if-color- suffixes; values must be "#rrggbb".
|
|
459
|
+
def inline_forms_color_overrides
|
|
460
|
+
{ 'primary' => primary_color, 'accent' => accent_color }.compact
|
|
461
|
+
end
|
|
462
|
+
|
|
418
463
|
def inline_forms_attribute_list
|
|
419
464
|
@inline_forms_attribute_list ||= [
|
|
420
465
|
[ :header_user_login, :header ],
|
|
421
466
|
[ :name, :text_field ],
|
|
422
467
|
[ :email, :text_field ],
|
|
423
468
|
[ :locale, :dropdown ],
|
|
469
|
+
[ :theme, :dropdown_with_values, { 0 => 'default', 1 => 'dark', 2 => 'sepia', 3 => 'high contrast' } ],
|
|
470
|
+
[ :primary_color, :color_field ],
|
|
471
|
+
[ :accent_color, :color_field ],
|
|
424
472
|
[ :password, :devise_password_field ],
|
|
425
473
|
[ :header_user_roles, :header ],
|
|
426
474
|
[ :roles, :check_list ],
|
|
@@ -654,24 +702,36 @@ create_file "app/controllers/application_controller.rb", <<-END_APPCONTROLLER.st
|
|
|
654
702
|
end
|
|
655
703
|
# Comment previous lines if you don't want Devise authentication
|
|
656
704
|
|
|
657
|
-
#
|
|
658
|
-
#
|
|
659
|
-
|
|
660
|
-
#
|
|
661
|
-
#
|
|
705
|
+
# Per-user locale (inline_forms 8.1.31): the signed-in user's Locale
|
|
706
|
+
# record (its `name`: en/nl/...) drives I18n for the request. Unknown or
|
|
707
|
+
# blank names fall back to I18n.default_locale, and I18n.with_locale
|
|
708
|
+
# restores the previous locale afterwards, so nothing leaks across
|
|
709
|
+
# requests or threads. Users pick their locale on their own user panel
|
|
710
|
+
# (the [:locale, :dropdown] row).
|
|
711
|
+
around_action :switch_locale
|
|
712
|
+
|
|
713
|
+
def switch_locale(&action)
|
|
714
|
+
candidate = current_user&.locale&.name.to_s.strip
|
|
715
|
+
locale =
|
|
716
|
+
if I18n.available_locales.map(&:to_s).include?(candidate)
|
|
717
|
+
candidate
|
|
718
|
+
else
|
|
719
|
+
I18n.default_locale
|
|
720
|
+
end
|
|
721
|
+
I18n.with_locale(locale, &action)
|
|
722
|
+
end
|
|
662
723
|
|
|
663
|
-
#
|
|
664
|
-
#
|
|
724
|
+
# Restrict/extend the locale whitelist in config/application.rb:
|
|
725
|
+
# config.i18n.available_locales = [:en, :nl]
|
|
726
|
+
# config.i18n.default_locale = :en
|
|
665
727
|
|
|
666
|
-
# Uncomment
|
|
667
|
-
#
|
|
668
|
-
#
|
|
669
|
-
#
|
|
670
|
-
#
|
|
671
|
-
# def extract_locale_from_subdomain
|
|
728
|
+
# Uncomment if you prefer locale based on subdomain ('nl.example.com')
|
|
729
|
+
# instead of the per-user setting above:
|
|
730
|
+
# around_action :switch_locale_from_subdomain
|
|
731
|
+
# def switch_locale_from_subdomain(&action)
|
|
672
732
|
# locale = request.subdomains.first
|
|
673
|
-
#
|
|
674
|
-
# I18n.
|
|
733
|
+
# locale = nil unless locale && I18n.available_locales.map(&:to_s).include?(locale)
|
|
734
|
+
# I18n.with_locale(locale || I18n.default_locale, &action)
|
|
675
735
|
# end
|
|
676
736
|
end
|
|
677
737
|
END_APPCONTROLLER
|
|
@@ -698,6 +758,19 @@ create_file "app/models/ability.rb", <<-END_ABILITY.strip_heredoc
|
|
|
698
758
|
end
|
|
699
759
|
END_ABILITY
|
|
700
760
|
|
|
761
|
+
# SSL in production. `rails new` only enables these when Kamal is skipped
|
|
762
|
+
# (its proxy config takes over otherwise), but generated apps deploy via the
|
|
763
|
+
# shipped Capistrano/Unicorn setup behind an SSL-terminating reverse proxy —
|
|
764
|
+
# and the Devise mailer config below already assumes https. Enabling both
|
|
765
|
+
# matches the Rails 8 defaults and closes Brakeman's ForceSSL warning
|
|
766
|
+
# (full-gate CI scans the generated app). Serving plain HTTP internally?
|
|
767
|
+
# Re-comment these in your app's config/environments/production.rb.
|
|
768
|
+
say "- Enabling assume_ssl/force_ssl in environments/production.rb..."
|
|
769
|
+
gsub_file "config/environments/production.rb",
|
|
770
|
+
"# config.assume_ssl = true", "config.assume_ssl = true"
|
|
771
|
+
gsub_file "config/environments/production.rb",
|
|
772
|
+
"# config.force_ssl = true", "config.force_ssl = true"
|
|
773
|
+
|
|
701
774
|
# devise mailer stuff
|
|
702
775
|
say "- Injecting devise mailer stuff in environments/production.rb..."
|
|
703
776
|
# strip_heredoc_with_indent(2) became strip_heredoc(2), but only in rails 4... :-(
|
|
@@ -776,6 +849,32 @@ copy_file File.join(INSTALLER_ROOT,'lib/installer_templates/capistrano/Capfile')
|
|
|
776
849
|
say "- Unicorn Config..."
|
|
777
850
|
copy_file File.join(INSTALLER_ROOT,'lib/installer_templates/unicorn/production.rb'), "config/unicorn/production.rb"
|
|
778
851
|
|
|
852
|
+
# Schema-change GUI (dev-only authoring; InlineForms::SchemaController from
|
|
853
|
+
# the inline_forms_schema_edit gem). Wired only when requested via
|
|
854
|
+
# `--schema-edit` (implied by `--example`): routes + a "+ field" nav link.
|
|
855
|
+
# Applies inline_forms_addto through the browser; does NOT run db:migrate.
|
|
856
|
+
# Must precede the example section: its test gate exercises these routes.
|
|
857
|
+
if ENV['install_schema_edit'] == 'true'
|
|
858
|
+
say "- Schema GUI: routes + nav link + batch tables..."
|
|
859
|
+
# One line: the gem owns its route set, so gem upgrades can add routes
|
|
860
|
+
# without editing the app's routes.rb.
|
|
861
|
+
route 'InlineFormsSchemaEdit.draw_routes(self)'
|
|
862
|
+
|
|
863
|
+
# Batch-pipeline tables (drafted intents + batches). The generator writes
|
|
864
|
+
# the migration; migrate here for sqlite (mysql apps migrate by hand, same
|
|
865
|
+
# policy as the main install).
|
|
866
|
+
run "bundle exec rails g inline_forms_schema_edit:install"
|
|
867
|
+
run "bundle exec rake db:migrate" if ENV['using_sqlite'] == 'true'
|
|
868
|
+
|
|
869
|
+
if File.exist?("app/views/_inline_forms_tabs.html.erb")
|
|
870
|
+
inject_into_file "app/views/_inline_forms_tabs.html.erb",
|
|
871
|
+
" <li class=\"menu-text\">\n" \
|
|
872
|
+
" <a href=\"/schema/new\" data-turbo=\"false\" title=\"Add a field to a model (dev only)\">+ field</a>\n" \
|
|
873
|
+
" </li>\n",
|
|
874
|
+
before: " </ul>\n </div>\n <div class=\"top-bar-right\">"
|
|
875
|
+
end
|
|
876
|
+
end
|
|
877
|
+
|
|
779
878
|
# Git
|
|
780
879
|
say "- adding and committing to git..."
|
|
781
880
|
|
|
@@ -785,8 +884,16 @@ git commit: " -a -m 'Initial Commit'"
|
|
|
785
884
|
# example
|
|
786
885
|
if ENV['install_example'] == 'true'
|
|
787
886
|
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}\''
|
|
887
|
+
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
888
|
run 'bundle exec rails generate uploader Image'
|
|
889
|
+
|
|
890
|
+
# Comment: second nesting level (Apartment -> Photo -> Comment) so the
|
|
891
|
+
# example app demonstrates :associated panels inside an opened nested row.
|
|
892
|
+
# Like Photo it has no `_enabled`, so it is not accessible through
|
|
893
|
+
# top-level HTML — only through the comments panel on an opened photo.
|
|
894
|
+
say "- Generating Comment model (nested under Photo -- nesting-in-nesting demo)..."
|
|
895
|
+
sleep 1
|
|
896
|
+
run 'bundle exec rails g inline_forms Comment body:text_area photo:belongs_to _list_order:body _presentation:\'#{body}\''
|
|
790
897
|
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
898
|
|
|
792
899
|
say "- Apartment name is required..."
|
|
@@ -861,23 +968,18 @@ if ENV['install_example'] == 'true'
|
|
|
861
968
|
|
|
862
969
|
run 'bundle exec rake db:migrate'
|
|
863
970
|
|
|
864
|
-
# Seed the photos gallery
|
|
865
|
-
#
|
|
866
|
-
#
|
|
867
|
-
#
|
|
868
|
-
#
|
|
869
|
-
#
|
|
870
|
-
#
|
|
871
|
-
#
|
|
872
|
-
#
|
|
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.
|
|
971
|
+
# Seed the photos gallery. The 9 CC0 placeholder images (solid pastel +
|
|
972
|
+
# "Apt N / Room" label, ~4 KB each, generated with ImageMagick — see
|
|
973
|
+
# stuff/2026-07-06-forgejo-ci-rubocop-brakeman.md) ship with the installer
|
|
974
|
+
# under lib/installer_templates/example_app_assets/seed_images/, so every
|
|
975
|
+
# install — laptop, CI container, anywhere — gets the same gallery. Before
|
|
976
|
+
# 8.1.35 this looked for a *gitignored* local pics/ folder, which existed
|
|
977
|
+
# only on the maintainer's box: gem installs elsewhere (e.g. the Forgejo
|
|
978
|
+
# full-gate runner) silently produced zero photos and 12 test failures.
|
|
979
|
+
# ENV['INLINE_FORMS_SEED_PICS'] still overrides with your own folder.
|
|
877
980
|
pics_candidates = [
|
|
878
981
|
ENV["INLINE_FORMS_SEED_PICS"],
|
|
879
|
-
File.join(INSTALLER_ROOT, "
|
|
880
|
-
"/home/code/inline_forms/pics",
|
|
982
|
+
File.join(INSTALLER_ROOT, "lib/installer_templates/example_app_assets/seed_images"),
|
|
881
983
|
].compact
|
|
882
984
|
pics_src = pics_candidates.find { |p| Dir.exist?(p) }
|
|
883
985
|
if pics_src
|
|
@@ -1043,6 +1145,13 @@ if ENV['install_example'] == 'true'
|
|
|
1043
1145
|
apartments: [] },
|
|
1044
1146
|
].freeze
|
|
1045
1147
|
|
|
1148
|
+
# Comments seeded on each apartment's first photo (Apartment ->
|
|
1149
|
+
# Photo -> Comment: the second nesting level).
|
|
1150
|
+
COMMENTS = [
|
|
1151
|
+
"Lovely light in this room!",
|
|
1152
|
+
"Could use a fresh coat of paint.",
|
|
1153
|
+
].freeze
|
|
1154
|
+
|
|
1046
1155
|
def up
|
|
1047
1156
|
seed_dir = Rails.root.join("db", "seed_images")
|
|
1048
1157
|
all_pics = seed_dir.directory? ?
|
|
@@ -1073,6 +1182,15 @@ if ENV['install_example'] == 'true'
|
|
|
1073
1182
|
end
|
|
1074
1183
|
end
|
|
1075
1184
|
|
|
1185
|
+
# Nesting-in-nesting demo data: two comments on each apartment's
|
|
1186
|
+
# first photo, so the comments panel inside an opened photo row
|
|
1187
|
+
# is visibly populated out of the box.
|
|
1188
|
+
if (first_photo = apt.photos.order(:id).first)
|
|
1189
|
+
COMMENTS.each do |body|
|
|
1190
|
+
Comment.find_or_create_by!(photo: first_photo, body: body)
|
|
1191
|
+
end
|
|
1192
|
+
end
|
|
1193
|
+
|
|
1076
1194
|
apt_records[spec[:name]] = apt
|
|
1077
1195
|
end
|
|
1078
1196
|
|
|
@@ -1099,6 +1217,7 @@ if ENV['install_example'] == 'true'
|
|
|
1099
1217
|
APARTMENTS.each do |spec|
|
|
1100
1218
|
apt = Apartment.find_by(name: spec[:name])
|
|
1101
1219
|
next unless apt
|
|
1220
|
+
apt.photos.each { |photo| photo.comments.destroy_all }
|
|
1102
1221
|
apt.photos.destroy_all
|
|
1103
1222
|
apt.destroy
|
|
1104
1223
|
end
|
|
@@ -1119,7 +1238,7 @@ if ENV['install_example'] == 'true'
|
|
|
1119
1238
|
# ---------------------------------------------------------------------
|
|
1120
1239
|
say "- Generating FormElementShowcase (one resource per kept Tier 1 form_element)..."
|
|
1121
1240
|
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}'}
|
|
1241
|
+
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
1242
|
|
|
1124
1243
|
say "- Generating Attachment + Jingle uploaders (Cover reuses ImageUploader)..."
|
|
1125
1244
|
run "bundle exec rails generate uploader Attachment"
|
|
@@ -1150,6 +1269,9 @@ if ENV['install_example'] == 'true'
|
|
|
1150
1269
|
showcase_migration = Dir.glob("db/migrate/*_inline_forms_create_form_element_showcases.rb").first
|
|
1151
1270
|
if showcase_migration
|
|
1152
1271
|
gsub_file showcase_migration, /t\.integer :amount\b/, "t.integer :amount_cents, default: 0, null: false"
|
|
1272
|
+
# multi_image_field stores a JSON array of filenames (mount_uploaders);
|
|
1273
|
+
# the registry maps it to :string, widen to :text for the serialized list.
|
|
1274
|
+
gsub_file showcase_migration, /t\.string :gallery\b/, "t.text :gallery"
|
|
1153
1275
|
end
|
|
1154
1276
|
|
|
1155
1277
|
create_file "config/initializers/money.rb", <<-MONEY_INIT.strip_heredoc
|
|
@@ -1185,7 +1307,9 @@ if ENV['install_example'] == 'true'
|
|
|
1185
1307
|
" validates :price, numericality: true, allow_blank: true\n" \
|
|
1186
1308
|
" validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_blank: true\n" \
|
|
1187
1309
|
" 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
|
|
1310
|
+
" mount_uploader :attachment, AttachmentUploader\n" \
|
|
1311
|
+
" serialize :gallery, coder: JSON\n mount_uploaders :gallery, ImageUploader\n" \
|
|
1312
|
+
" monetize :amount_cents\n\n def locales_display\n locales\n end\n",
|
|
1189
1313
|
after: "class FormElementShowcase < ApplicationRecord\n"
|
|
1190
1314
|
|
|
1191
1315
|
# Value-bearing rows for every form_element that needs a values hash
|
|
@@ -1272,6 +1396,7 @@ if ENV['install_example'] == 'true'
|
|
|
1272
1396
|
attachment: Attachment
|
|
1273
1397
|
jingle: Jingle
|
|
1274
1398
|
cover: Cover
|
|
1399
|
+
gallery: Gallery (multiple images)
|
|
1275
1400
|
description: Description
|
|
1276
1401
|
locales: Locales (editable)
|
|
1277
1402
|
locales_display: Locales (read-only)
|
|
@@ -1382,6 +1507,13 @@ if ENV['install_example'] == 'true'
|
|
|
1382
1507
|
next if full.public_send(attr).present?
|
|
1383
1508
|
File.open(path, "rb") { |io| full.public_send("\#{attr}=", io) }
|
|
1384
1509
|
end
|
|
1510
|
+
|
|
1511
|
+
# multi_image_field demo: an *array* assignment through the plural
|
|
1512
|
+
# mount_uploaders. One file is enough to exercise the array path.
|
|
1513
|
+
gallery_src = seed_uploads.join("sample_cover.png")
|
|
1514
|
+
if full.respond_to?(:gallery) && gallery_src.file? && full.gallery.blank?
|
|
1515
|
+
File.open(gallery_src, "rb") { |io| full.gallery = [io] }
|
|
1516
|
+
end
|
|
1385
1517
|
full.save! if full.changed?
|
|
1386
1518
|
|
|
1387
1519
|
# "Empty" refers to the role and uploader fields (their empty
|
|
@@ -1410,6 +1542,16 @@ if ENV['install_example'] == 'true'
|
|
|
1410
1542
|
say "- Running showcase migrations (create table + join + seed)..."
|
|
1411
1543
|
run "bundle exec rake db:migrate"
|
|
1412
1544
|
|
|
1545
|
+
# End-to-end exercise of `rails g inline_forms_addto` on an existing model.
|
|
1546
|
+
# Apartment is _enabled and uses the stock inline_forms show (Owner's tabbed
|
|
1547
|
+
# controller whitelists fields, so a new field would be hidden there). This
|
|
1548
|
+
# proves generate + migrate + boot + render + update against a real app; the
|
|
1549
|
+
# accompanying integration test is example_app_apartment_addto_field_test.rb.
|
|
1550
|
+
say "- addto demo: adding Apartment#internal_note via inline_forms_addto..."
|
|
1551
|
+
sleep 1
|
|
1552
|
+
run "bundle exec rails g inline_forms_addto Apartment internal_note:string"
|
|
1553
|
+
run "bundle exec rake db:migrate"
|
|
1554
|
+
|
|
1413
1555
|
example_views_root = File.join(INSTALLER_ROOT, "lib/installer_templates/example_app_views")
|
|
1414
1556
|
Dir.glob(File.join(example_views_root, "**", "*")).sort.each do |abs|
|
|
1415
1557
|
next unless File.file?(abs)
|
|
@@ -1418,6 +1560,7 @@ if ENV['install_example'] == 'true'
|
|
|
1418
1560
|
end
|
|
1419
1561
|
|
|
1420
1562
|
route 'get "apartments/name_list", to: "apartments#name_list", as: :apartment_name_list'
|
|
1563
|
+
|
|
1421
1564
|
route "root :to => 'apartments#index'"
|
|
1422
1565
|
|
|
1423
1566
|
example_tests_root = File.join(INSTALLER_ROOT, "lib/installer_templates/example_app_tests")
|
|
@@ -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
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../example_app/example_integration_test_case"
|
|
4
|
+
|
|
5
|
+
# End-to-end coverage for `rails g inline_forms_addto Apartment
|
|
6
|
+
# internal_note:string`, which the installer runs during --example. Proves the
|
|
7
|
+
# generated column migrated, the new attribute_list row renders on the stock
|
|
8
|
+
# show panel, and an inline edit/update round-trips through the generic
|
|
9
|
+
# controller.
|
|
10
|
+
class ExampleAppApartmentAddtoFieldTest < ExampleAppIntegrationTestCase
|
|
11
|
+
setup do
|
|
12
|
+
@apartment = Apartment.find_or_create_by!(name: "Addto Field Apt") do |a|
|
|
13
|
+
a.title = "Addto Field Title"
|
|
14
|
+
end
|
|
15
|
+
@frame_id = "apartment_#{@apartment.id}_internal_note"
|
|
16
|
+
@turbo_headers = { "Turbo-Frame" => @frame_id }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "internal_note column exists after the addto migration" do
|
|
20
|
+
assert_includes Apartment.column_names, "internal_note",
|
|
21
|
+
"inline_forms_addto migration should have added the internal_note column"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test "internal_note row renders on the stock show panel" do
|
|
25
|
+
row_frame = "apartment_#{@apartment.id}"
|
|
26
|
+
get apartment_path(@apartment, update: row_frame),
|
|
27
|
+
headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
|
|
28
|
+
|
|
29
|
+
assert_response :success
|
|
30
|
+
assert_includes @response.body, @frame_id,
|
|
31
|
+
"the addto attribute_list row should render its own per-field turbo-frame"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
test "internal_note inline edit and update round-trips" do
|
|
35
|
+
get edit_apartment_path(
|
|
36
|
+
@apartment,
|
|
37
|
+
attribute: "internal_note",
|
|
38
|
+
form_element: "text_field",
|
|
39
|
+
update: @frame_id
|
|
40
|
+
), headers: @turbo_headers
|
|
41
|
+
assert_response :success
|
|
42
|
+
assert_includes @response.body, %(<turbo-frame id="#{@frame_id}">)
|
|
43
|
+
|
|
44
|
+
put apartment_path(
|
|
45
|
+
@apartment,
|
|
46
|
+
attribute: "internal_note",
|
|
47
|
+
form_element: "text_field",
|
|
48
|
+
update: @frame_id
|
|
49
|
+
), params: { internal_note: "Boiler serviced 2026-07" }, headers: @turbo_headers
|
|
50
|
+
assert_response :success
|
|
51
|
+
assert_includes @response.body, "Boiler serviced 2026-07"
|
|
52
|
+
assert_equal "Boiler serviced 2026-07", @apartment.reload.internal_note
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -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 (
|
|
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
|
|
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, %(
|
|
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
|
-
"
|
|
40
|
+
"native inputs need no per-field init script"
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
test "new apartment form includes
|
|
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, %(
|
|
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
|
data/lib/installer_templates/example_app_tests/test/integration/example_app_photo_comments_test.rb
ADDED
|
@@ -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
|
data/lib/installer_templates/example_app_tests/test/integration/example_app_production_ssl_test.rb
ADDED
|
@@ -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
|
data/lib/installer_templates/example_app_tests/test/integration/example_app_schema_edit_test.rb
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../example_app/example_integration_test_case"
|
|
4
|
+
|
|
5
|
+
# The dev-only schema-change GUI (InlineForms::SchemaController), mounted only
|
|
6
|
+
# in the example app. Non-destructive coverage: the new form renders and a
|
|
7
|
+
# preview of a brand-new scalar field on Apartment renders with no schema
|
|
8
|
+
# change. Apply (the actual generator run) is covered by the engine dummy suite
|
|
9
|
+
# (recording executor) and inline_forms_addto_generator_test, so this test does
|
|
10
|
+
# not mutate the generated app during the shared gate.
|
|
11
|
+
class ExampleAppSchemaEditTest < ExampleAppIntegrationTestCase
|
|
12
|
+
test "schema/new renders the add-a-field form" do
|
|
13
|
+
get "/schema/new"
|
|
14
|
+
assert_response :success
|
|
15
|
+
assert_includes @response.body, "Form element"
|
|
16
|
+
assert_includes @response.body, "text_field"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "schema/preview shows a new scalar field on Apartment without changing the schema" do
|
|
20
|
+
refute_includes Apartment.column_names, "staff_note",
|
|
21
|
+
"precondition: staff_note must not already exist"
|
|
22
|
+
|
|
23
|
+
post "/schema/preview",
|
|
24
|
+
params: { model_name: "Apartment", attribute: "staff_note",
|
|
25
|
+
form_element: "text_field", after: "name" }
|
|
26
|
+
|
|
27
|
+
assert_response :success
|
|
28
|
+
assert_includes @response.body, "staff_note (new)"
|
|
29
|
+
assert_includes @response.body, %(name="staff_note")
|
|
30
|
+
|
|
31
|
+
Apartment.reset_column_information
|
|
32
|
+
refute_includes Apartment.column_names, "staff_note",
|
|
33
|
+
"preview must not add a column"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Batch pipeline (phases 1-3): the install generator created the tables,
|
|
37
|
+
# so draft -> submit -> token export works end to end in a generated app.
|
|
38
|
+
# No codegen happens (batch mode never generates in the request cycle), so
|
|
39
|
+
# this cannot mutate the app during the shared gate.
|
|
40
|
+
test "batch drafting, freeze on submit, and token-authenticated export" do
|
|
41
|
+
post "/schema/draft",
|
|
42
|
+
params: { model_name: "Apartment", attribute: "staff_note",
|
|
43
|
+
form_element: "text_field", after: "name", label: "Staff note" }
|
|
44
|
+
assert_response :redirect
|
|
45
|
+
|
|
46
|
+
batch = InlineForms::SchemaBatch.with_status(:draft).last
|
|
47
|
+
assert_equal 1, batch.intents.count
|
|
48
|
+
|
|
49
|
+
get "/schema"
|
|
50
|
+
assert_response :success
|
|
51
|
+
assert_includes @response.body, "staff_note"
|
|
52
|
+
|
|
53
|
+
post "/schema/batch/submit"
|
|
54
|
+
batch.reload
|
|
55
|
+
assert_equal "submitted", batch.status
|
|
56
|
+
assert_match(/\Asha256:/, batch.content_digest)
|
|
57
|
+
refute batch.intents.first.update(label: "changed"), "frozen after submit"
|
|
58
|
+
|
|
59
|
+
original_token = InlineFormsSchemaEdit.export_token
|
|
60
|
+
InlineFormsSchemaEdit.export_token = "gate-test-token"
|
|
61
|
+
begin
|
|
62
|
+
get "/schema/batches/#{batch.id}/export.json"
|
|
63
|
+
assert_response :unauthorized, "token configured but not provided"
|
|
64
|
+
|
|
65
|
+
get "/schema/batches/#{batch.id}/export.json",
|
|
66
|
+
headers: { "Authorization" => "Bearer gate-test-token" }
|
|
67
|
+
assert_response :success
|
|
68
|
+
payload = JSON.parse(@response.body)
|
|
69
|
+
assert_equal batch.content_digest, payload["digest"]
|
|
70
|
+
assert_equal "staff_note", payload["intents"].first["attribute"]
|
|
71
|
+
ensure
|
|
72
|
+
InlineFormsSchemaEdit.export_token = original_token
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
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
|
|
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, %(
|
|
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
|
|
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, %(
|
|
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
|
|
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, %(
|
|
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
|
data/lib/installer_templates/example_app_tests/test/integration/example_app_showcase_gallery_test.rb
ADDED
|
@@ -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
|
data/lib/installer_templates/example_app_tests/test/integration/example_app_user_locale_test.rb
ADDED
|
@@ -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
|
data/lib/installer_templates/example_app_tests/test/integration/example_app_user_theme_test.rb
ADDED
|
@@ -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? (
|
|
6
|
-
InlineForms::TurboTabsBuilder threads
|
|
7
|
-
|
|
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'> </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.
|
|
4
|
+
version: 8.1.42
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ace Suares
|
|
@@ -77,7 +77,17 @@ 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
|
|
90
|
+
- lib/installer_templates/example_app_tests/test/integration/example_app_apartment_addto_field_test.rb
|
|
81
91
|
- lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb
|
|
82
92
|
- lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb
|
|
83
93
|
- lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_required_test.rb
|
|
@@ -89,11 +99,15 @@ files:
|
|
|
89
99
|
- lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb
|
|
90
100
|
- lib/installer_templates/example_app_tests/test/integration/example_app_guest_access_test.rb
|
|
91
101
|
- lib/installer_templates/example_app_tests/test/integration/example_app_owner_tabs_test.rb
|
|
102
|
+
- lib/installer_templates/example_app_tests/test/integration/example_app_photo_comments_test.rb
|
|
92
103
|
- lib/installer_templates/example_app_tests/test/integration/example_app_photo_revert_test.rb
|
|
93
104
|
- lib/installer_templates/example_app_tests/test/integration/example_app_photos_test.rb
|
|
105
|
+
- lib/installer_templates/example_app_tests/test/integration/example_app_production_ssl_test.rb
|
|
94
106
|
- lib/installer_templates/example_app_tests/test/integration/example_app_routing_test.rb
|
|
107
|
+
- lib/installer_templates/example_app_tests/test/integration/example_app_schema_edit_test.rb
|
|
95
108
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_choice_scale_fields_test.rb
|
|
96
109
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_date_time_fields_test.rb
|
|
110
|
+
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_gallery_test.rb
|
|
97
111
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_locales_associations_test.rb
|
|
98
112
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_money_update_test.rb
|
|
99
113
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_numeric_fields_test.rb
|
|
@@ -102,6 +116,8 @@ files:
|
|
|
102
116
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_text_fields_test.rb
|
|
103
117
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_validation_hints_test.rb
|
|
104
118
|
- lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb
|
|
119
|
+
- lib/installer_templates/example_app_tests/test/integration/example_app_user_locale_test.rb
|
|
120
|
+
- lib/installer_templates/example_app_tests/test/integration/example_app_user_theme_test.rb
|
|
105
121
|
- lib/installer_templates/example_app_tests/test/integration/example_app_validation_hints_test.rb
|
|
106
122
|
- lib/installer_templates/example_app_tests/test/models/example_app_apartment_name_validation_test.rb
|
|
107
123
|
- lib/installer_templates/example_app_tests/test/models/example_app_apartment_photo_test.rb
|